Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Imporoved test coverage of client requests #11

Merged
merged 4 commits into from
Aug 25, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 111 additions & 15 deletions src/CloudFlare.NET/CloudFlareClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,124 @@
/// <inheritdoc/>
public class CloudFlareClient : ICloudFlareClient
{
private readonly HttpClient _client;

private readonly CloudFlareAuth _auth;

private static readonly Lazy<HttpClient> LazyClient = new Lazy<HttpClient>(
() =>
() => CreateDefaultHttpClient(),
LazyThreadSafetyMode.PublicationOnly);

/// <summary>
/// Initializes a new instance of the <see cref="CloudFlareClient"/> class.
/// </summary>
/// <param name="auth">
/// <para>The authentication details for accessing the CloudFlare API.</para>
/// <para>If authentication details are not provided via the constructor, they must be provided to every method
/// call.</para>
/// </param>
public CloudFlareClient(CloudFlareAuth auth = null)
: this(LazyClient.Value, auth)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="CloudFlareClient"/> class.
/// </summary>
/// <param name="client">A custom <see cref="HttpClient"/> to be used instead of the internal client.</param>
/// <param name="auth">
/// <para>The authentication details for accessing the CloudFlare API.</para>
/// <para>If authentication details are not provided via the constructor, they must be provided to every method
/// call.</para>
/// </param>
public CloudFlareClient(HttpClient client, CloudFlareAuth auth = null)
{
if (client == null)
throw new ArgumentNullException(nameof(client));

_client = client;
_auth = auth;
}

/// <summary>
/// Creates the default <see cref="HttpClient"/>
/// </summary>
/// <param name="handlers">
/// Chain of <see cref="HttpMessageHandler" /> instances.
/// All but the last should be <see cref="DelegatingHandler"/>s.
/// </param>
/// <returns>The default <see cref="HttpClient"/>.</returns>
public static HttpClient CreateDefaultHttpClient(params HttpMessageHandler[] handlers)
{
if (handlers == null)
throw new ArgumentNullException(nameof(handlers));

var client = new HttpClient(CreatePipeline(handlers));

client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

return client;
}

/// <summary>
/// Transform a collection of <see cref="HttpMessageHandler"/>s into a chain of
/// <see cref="HttpMessageHandler"/>s.
/// </summary>
/// <param name="handlers">
/// Chain of <see cref="HttpMessageHandler" /> instances.
/// All but the last should be <see cref="DelegatingHandler"/>s.
/// </param>
/// <returns>A chain of <see cref="HttpMessageHandler"/>s</returns>
public static HttpMessageHandler CreatePipeline(IReadOnlyCollection<HttpMessageHandler> handlers)
{
if (handlers == null)
throw new ArgumentNullException(nameof(handlers));

HttpMessageHandler pipeline = handlers.LastOrDefault() ?? CreateDefaultHttpClientHandler();
DelegatingHandler dHandler = pipeline as DelegatingHandler;
if (dHandler != null)
{
var client = new HttpClient(
new HttpClientHandler
{
AutomaticDecompression = DecompressionMethods.GZip,
})
;
dHandler.InnerHandler = CreateDefaultHttpClientHandler();
pipeline = dHandler;
}

client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// Wire handlers up in reverse order
IEnumerable<HttpMessageHandler> reversedHandlers = handlers.Reverse().Skip(1);
foreach (HttpMessageHandler handler in reversedHandlers)
{
dHandler = handler as DelegatingHandler;
if (dHandler == null)
{
throw new ArgumentException(
$"All message handlers except the last must be of type '{typeof(DelegatingHandler).Name}'.",
nameof(handlers));
}

return client;
},
LazyThreadSafetyMode.PublicationOnly);
dHandler.InnerHandler = pipeline;
pipeline = dHandler;
}

private static HttpClient Client => LazyClient.Value;
return pipeline;
}

/// <summary>
/// Returns a default HttpMessageHandler that supports automatic decompression.
/// </summary>
public static HttpClientHandler CreateDefaultHttpClientHandler()
{
var handler = new HttpClientHandler();
if (handler.SupportsAutomaticDecompression)
{
handler.AutomaticDecompression = DecompressionMethods.GZip;
}

return handler;
}

/// <inheritdoc/>
public Task<IReadOnlyList<Zone>> GetZonesAsync(CancellationToken cancellationToken, CloudFlareAuth auth = null)
{
return Client.GetZonesAsync(cancellationToken, auth);
return _client.GetZonesAsync(cancellationToken, auth ?? _auth);
}

/// <inheritdoc/>
Expand All @@ -42,7 +138,7 @@ public Task<Zone> GetZoneAsync(
CancellationToken cancellationToken,
CloudFlareAuth auth = null)
{
return Client.GetZoneAsync(zoneId, cancellationToken, auth);
return _client.GetZoneAsync(zoneId, cancellationToken, auth ?? _auth);
}

/// <inheritdoc/>
Expand All @@ -51,7 +147,7 @@ public Task<IReadOnlyList<DnsRecord>> GetDnsRecordsAsync(
CancellationToken cancellationToken,
CloudFlareAuth auth = null)
{
return Client.GetDnsRecordsAsync(zoneId, cancellationToken, auth);
return _client.GetDnsRecordsAsync(zoneId, cancellationToken, auth ?? _auth);
}
}
}
2 changes: 1 addition & 1 deletion src/CloudFlare.NET/IModified.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
using Newtonsoft.Json;

/// <summary>
/// Defines the <see cref="CreatedOn"/> and <see cref="ModifiedOn"/> properies of an entity.
/// Defines the <see cref="CreatedOn"/> and <see cref="ModifiedOn"/> properties of an entity.
/// </summary>
public interface IModified
{
Expand Down
5 changes: 5 additions & 0 deletions src/Tests/CloudFlare.NET.Tests/CloudFlare.NET.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -100,14 +100,18 @@
</ItemGroup>
<ItemGroup>
<Compile Include="CloudFlareAuthSpec.cs" />
<Compile Include="CloudFlareClientSpec.cs" />
<Compile Include="CloudFlareCustomization.cs" />
<Compile Include="CloudFlareErrorSpec.cs" />
<Compile Include="DnsRecordClientExtensionsSpec.cs" />
<Compile Include="DnsRecordClientSpec.cs" />
<Compile Include="EqualsBehaviors.cs" />
<Compile Include="FixtureContext.cs" />
<Compile Include="HttpHandlers\TestHttpHandler.cs" />
<Compile Include="IdentifierTagSpec.cs" />
<Compile Include="IsNullable.cs" />
<Compile Include="LikenessExtensions.cs" />
<Compile Include="RequestBehaviorTemplates.cs" />
<Compile Include="RequiresArgNullEx.cs" />
<Compile Include="RequiresArgNullExAutoMoqAttribute.cs" />
<Compile Include="Serialization\BehaviorTemplates.cs" />
Expand All @@ -118,6 +122,7 @@
<Compile Include="Serialization\SuccessResponseSerializationSpec.cs" />
<Compile Include="Serialization\ZoneSerializationSpec.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ZoneClientSpec.cs" />
<None Include="app.config" />
<None Include="packages.config" />
<EmbeddedResource Include="Serialization\DnsRecord.json" />
Expand Down
102 changes: 102 additions & 0 deletions src/Tests/CloudFlare.NET.Tests/CloudFlareClientSpec.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
namespace CloudFlare.NET
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using Machine.Specifications;
using Ploeh.AutoFixture;

[Subject(typeof(CloudFlareClient))]
public class When_creating_the_message_handler_pipeline_using_only_delegates : FixtureContext
{
static IReadOnlyCollection<DelegatingHandler> _handlers;
static HttpMessageHandler _result;

Establish context = () => _handlers = _fixture.Create<DelegatingHandler[]>();

Because of = () => _result = CloudFlareClient.CreatePipeline(_handlers);

It should_start_the_chain_with_the_first_handler = () => _result.ShouldBeTheSameAs(_handlers.First());

It should_create_a_chain_of_delegates = () =>
{
HttpMessageHandler current = _result;
foreach (DelegatingHandler handler in _handlers)
{
current.ShouldBeTheSameAs(handler);
current = handler.InnerHandler;
}
};

It should_end_the_chain_with_a_HttpClientHandler = () =>
{
HttpMessageHandler current = _result;
DelegatingHandler @delegate;
while ((@delegate = current as DelegatingHandler) != null)
{
current = @delegate.InnerHandler;
}

current.ShouldBeOfExactType<HttpClientHandler>();
};
}

[Subject(typeof(CloudFlareClient))]
public class When_creating_the_message_handler_pipeline_with_a_HttpClientHandler_at_the_end : FixtureContext
{
static List<HttpMessageHandler> _handlers;
static HttpClientHandler _last;
static HttpMessageHandler _result;

Establish context = () =>
{
_last = new HttpClientHandler();
_handlers = _fixture.Create<DelegatingHandler[]>().ToList<HttpMessageHandler>();
_handlers.Add(_last);
};

Because of = () => _result = CloudFlareClient.CreatePipeline(_handlers);

It should_start_the_chain_with_the_first_handler = () => _result.ShouldBeTheSameAs(_handlers.First());

It should_create_a_chain_of_delegates = () =>
{
HttpMessageHandler current = _result;
foreach (DelegatingHandler handler in _handlers.OfType<DelegatingHandler>())
{
current.ShouldBeTheSameAs(handler);
current = handler.InnerHandler;
}
};

It should_end_the_chain_with_the_last_HttpClientHandler = () =>
{
HttpMessageHandler current = _result;
DelegatingHandler @delegate;
while ((@delegate = current as DelegatingHandler) != null)
{
current = @delegate.InnerHandler;
}

current.ShouldBeTheSameAs(_last);
};
}

[Subject(typeof(CloudFlareClient))]
public class When_creating_a_pipeline_with_a_HttpClientHandler_at_the_front : FixtureContext
{
static List<HttpMessageHandler> _handlers;
static Exception _exception;

Establish context = () =>
{
_handlers = _fixture.Create<DelegatingHandler[]>().ToList<HttpMessageHandler>();
_handlers.Insert(0, new HttpClientHandler());
};

Because of = () => _exception = Catch.Exception(() => CloudFlareClient.CreatePipeline(_handlers));

It should_throw_an_ArgumentException = () => _exception.ShouldBeOfExactType<ArgumentException>();
}
}
1 change: 1 addition & 0 deletions src/Tests/CloudFlare.NET.Tests/CloudFlareCustomization.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ void ICustomization.Customize(IFixture fixture)
fixture.Register(() => new IdentifierTag(Guid.NewGuid().ToString("N")));
fixture.Register<IReadOnlyList<string>>(fixture.Create<string[]>);
fixture.Register<IReadOnlyList<CloudFlareError>>(fixture.Create<CloudFlareError[]>);
fixture.Register<IReadOnlyList<Zone>>(fixture.Create<Zone[]>);
fixture.Register(() => JObject.FromObject(fixture.Create<CloudFlareResponseBase>()));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Machine.Specifications;
using Moq;
using Ploeh.AutoFixture;
using MoqIt = Moq.It;
using It = Machine.Specifications.It;

[Subject(typeof(DnsRecordClientExtensions))]
Expand Down
38 changes: 38 additions & 0 deletions src/Tests/CloudFlare.NET.Tests/DnsRecordClientSpec.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
namespace CloudFlare.NET.DnsRecordClientSpec
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using Machine.Specifications;
using Ploeh.AutoFixture;

[Subject(typeof(CloudFlareClient))]
public class When_getting_dnsRecords : RequestContext
{
static IdentifierTag _zoneId;
static IReadOnlyList<DnsRecord> _expected;
static IReadOnlyList<DnsRecord> _actual;
static Uri _expectedRequestUri;

Establish context = () =>
{
_zoneId = _fixture.Create<IdentifierTag>();
var response = _fixture.Create<CloudFlareResponse<IReadOnlyList<DnsRecord>>>();
_expected = response.Result;
_handler.SetResponseContent(response);
_expectedRequestUri = new Uri(CloudFlareConstants.BaseUri, $"zones/{_zoneId}/dns_records");
};

Because of = () => _actual = _sut.GetDnsRecordsAsync(_zoneId, _auth).Await().AsTask.Result;

Behaves_like<AuthenticatedRequestBehaviour> authenticated_request_behaviour;

It should_make_a_GET_request = () => _handler.Request.Method.ShouldEqual(HttpMethod.Get);

It should_request_the_zones_endpoint = () => _handler.Request.RequestUri.ShouldEqual(_expectedRequestUri);

It should_return_the_expected_zones = () =>
_actual.Select(z => z.AsLikeness().CreateProxy()).SequenceEqual(_expected).ShouldBeTrue();
}
}
16 changes: 16 additions & 0 deletions src/Tests/CloudFlare.NET.Tests/FixtureContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ namespace CloudFlare.NET
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using CloudFlare.NET.HttpHandlers;
using Machine.Specifications;
using Ploeh.AutoFixture;

Expand All @@ -15,4 +17,18 @@ public abstract class FixtureContext
_fixture = new Fixture().Customize(new CloudFlareCustomization());
};
}

public abstract class RequestContext : FixtureContext
{
protected static TestHttpHandler _handler;
protected static CloudFlareClient _sut;
protected static CloudFlareAuth _auth;

Establish context = () =>
{
_handler = new TestHttpHandler();
_sut = new CloudFlareClient(new HttpClient(_handler), _fixture.Create<CloudFlareAuth>());
_auth = _fixture.Create<CloudFlareAuth>();
};
}
}
Loading