From b2b3fa73d1b6cbcc4bf470280f5b72862ca8d33e Mon Sep 17 00:00:00 2001 From: tmenier Date: Sun, 20 Sep 2015 11:54:16 -0500 Subject: [PATCH] ConfigureClient, plus lots of comments and some more tests --- Flurl.Http.Shared/ClientConfigExtensions.cs | 29 ++++++++ .../DefaultUrlEncodedSerializer.cs | 6 +- .../Configuration/ISerializer.cs | 12 ++++ .../Configuration/NewtonsoftJsonSerializer.cs | 3 +- .../HttpRequestMessageExtensions.cs | 11 ++- .../Flurl.Test.Shared.projitems | 2 +- .../Http/ClientConfigTests.cs | 53 ++++++++------- ...{SettingsTests.cs => GlobalConfigTests.cs} | 68 +++++++++++-------- 8 files changed, 122 insertions(+), 62 deletions(-) rename Test/Flurl.Test.Shared/Http/{SettingsTests.cs => GlobalConfigTests.cs} (70%) diff --git a/Flurl.Http.Shared/ClientConfigExtensions.cs b/Flurl.Http.Shared/ClientConfigExtensions.cs index 4507d13..a476454 100644 --- a/Flurl.Http.Shared/ClientConfigExtensions.cs +++ b/Flurl.Http.Shared/ClientConfigExtensions.cs @@ -4,6 +4,7 @@ using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Text; +using Flurl.Http.Configuration; using Flurl.Util; namespace Flurl.Http @@ -41,6 +42,34 @@ namespace Flurl.Http return client; } + /// + /// Change FlurlHttpSettings for this client instance. + /// + /// Action defining the settings changes. + /// The FlurlClient with the modified HttpClient + public static FlurlClient ConfigureClient(this FlurlClient client, Action action) { + action(client.Settings); + return client; + } + + /// + /// Creates a FlurlClient from the URL and allows changing the FlurlHttpSettings associated with the instance. + /// + /// Action defining the settings changes. + /// The FlurlClient with the modified HttpClient + public static FlurlClient ConfigureClient(this Url url, Action action) { + return new FlurlClient(url, true).ConfigureClient(action); + } + + /// + /// Creates a FlurlClient from the URL and allows changing the FlurlHttpSettings associated with the instance. + /// + /// Action defining the settings changes. + /// The FlurlClient with the modified HttpClient + public static FlurlClient ConfigureClient(this string url, Action action) { + return new FlurlClient(url, true).ConfigureClient(action); + } + /// /// Provides access to modifying the underlying HttpClient. /// diff --git a/Flurl.Http.Shared/Configuration/DefaultUrlEncodedSerializer.cs b/Flurl.Http.Shared/Configuration/DefaultUrlEncodedSerializer.cs index f02a595..ccb33e1 100644 --- a/Flurl.Http.Shared/Configuration/DefaultUrlEncodedSerializer.cs +++ b/Flurl.Http.Shared/Configuration/DefaultUrlEncodedSerializer.cs @@ -6,7 +6,11 @@ using Flurl.Util; namespace Flurl.Http.Configuration { - public class DefaultUrlEncodedSerializer : ISerializer + /// + /// ISerializer implementation that converts an object representing name/value pairs to a URL-encoded string. + /// Default serializer used in calls to PostUrlEncodedAsync, etc. + /// + public class DefaultUrlEncodedSerializer : ISerializer { public string Serialize(object obj) { var sb = new StringBuilder(); diff --git a/Flurl.Http.Shared/Configuration/ISerializer.cs b/Flurl.Http.Shared/Configuration/ISerializer.cs index 468e6fd..71b2a87 100644 --- a/Flurl.Http.Shared/Configuration/ISerializer.cs +++ b/Flurl.Http.Shared/Configuration/ISerializer.cs @@ -5,10 +5,22 @@ using System.Text; namespace Flurl.Http.Configuration { + /// + /// Contract for serializing and deserializing objects. + /// public interface ISerializer { + /// + /// Serializes an object to a string representation. + /// string Serialize(object obj); + /// + /// Deserializes an object from a string representation. + /// T Deserialize(string s); + /// + /// Deserializes an object from a stream representation. + /// T Deserialize(Stream stream); } } diff --git a/Flurl.Http.Shared/Configuration/NewtonsoftJsonSerializer.cs b/Flurl.Http.Shared/Configuration/NewtonsoftJsonSerializer.cs index 3b62716..e023a24 100644 --- a/Flurl.Http.Shared/Configuration/NewtonsoftJsonSerializer.cs +++ b/Flurl.Http.Shared/Configuration/NewtonsoftJsonSerializer.cs @@ -7,7 +7,8 @@ using Newtonsoft.Json; namespace Flurl.Http.Configuration { /// - /// ISerializer implementation that uses Newtonsoft Json.NET. Used as Flurl.Http's default JSON serializer. + /// ISerializer implementation that uses Newtonsoft Json.NET. + /// Default serializer used in calls to GetJsonAsync, PostJsonAsync, etc. /// public class NewtonsoftJsonSerializer : ISerializer { diff --git a/Flurl.Http.Shared/HttpRequestMessageExtensions.cs b/Flurl.Http.Shared/HttpRequestMessageExtensions.cs index 515313e..31d25a8 100644 --- a/Flurl.Http.Shared/HttpRequestMessageExtensions.cs +++ b/Flurl.Http.Shared/HttpRequestMessageExtensions.cs @@ -8,13 +8,18 @@ namespace Flurl.Http { public static class HttpRequestMessageExtensions { - public static void SetFlurlSettings(this HttpRequestMessage request, FlurlHttpSettings settings) { - request.Properties["FlurlSettings"] = settings; + private const string SETTINGS_KEY = "FlurlSettings"; + + internal static void SetFlurlSettings(this HttpRequestMessage request, FlurlHttpSettings settings) { + request.Properties[SETTINGS_KEY] = settings; } + /// + /// Gets the FlurlSettings object associated with this HttpRequestMessage. + /// public static FlurlHttpSettings GetFlurlSettings(this HttpRequestMessage request) { object settings; - return request.Properties.TryGetValue("FlurlSettings", out settings) ? (FlurlHttpSettings)settings : FlurlHttp.GlobalSettings; + return request.Properties.TryGetValue(SETTINGS_KEY, out settings) ? (FlurlHttpSettings)settings : FlurlHttp.GlobalSettings; } } } diff --git a/Test/Flurl.Test.Shared/Flurl.Test.Shared.projitems b/Test/Flurl.Test.Shared/Flurl.Test.Shared.projitems index 110d18a..eb227cf 100644 --- a/Test/Flurl.Test.Shared/Flurl.Test.Shared.projitems +++ b/Test/Flurl.Test.Shared/Flurl.Test.Shared.projitems @@ -16,7 +16,7 @@ - + diff --git a/Test/Flurl.Test.Shared/Http/ClientConfigTests.cs b/Test/Flurl.Test.Shared/Http/ClientConfigTests.cs index 51095dc..ae56b8a 100644 --- a/Test/Flurl.Test.Shared/Http/ClientConfigTests.cs +++ b/Test/Flurl.Test.Shared/Http/ClientConfigTests.cs @@ -4,14 +4,19 @@ using System.Linq; using System.Net; using System.Threading.Tasks; using Flurl.Http; +using Flurl.Http.Configuration; using Flurl.Http.Testing; using NUnit.Framework; namespace Flurl.Test.Http { [TestFixture] - public class ClientConfigTests + public class ClientConfigTestsBase : ConfigTestsBase { + protected override FlurlHttpSettings GetSettings() { + return GetClient().Settings; + } + [Test] public void can_set_timeout() { var client = "http://www.api.com".WithTimeout(TimeSpan.FromSeconds(15)); @@ -77,16 +82,25 @@ namespace Flurl.Test.Http } [Test] - public async Task can_allow_non_success_status() { + public async Task can_allow_specific_http_status() { + using (var test = new HttpTest()) { + test.RespondWith(404, "Nothing to see here"); + // no exception = pass + await "http://www.api.com" + .AllowHttpStatus(HttpStatusCode.Conflict, HttpStatusCode.NotFound) + .DeleteAsync(); + } + } + + [Test, ExpectedException(typeof(FlurlHttpException))] + public async Task can_clear_non_success_status() { using (var test = new HttpTest()) { test.RespondWith(418, "I'm a teapot"); - try { - var result = await "http://www.api.com".AllowHttpStatus("1xx,300-500").GetAsync(); - Assert.IsFalse(result.IsSuccessStatusCode); - } - catch (Exception) { - Assert.Fail("Exception should not have been thrown."); - } + // allow 4xx + var client = "http://www.api.com".AllowHttpStatus("4xx"); + // but then disallow it + client.Settings.AllowedHttpStatusRange = null; + await client.GetAsync(); } } @@ -105,24 +119,11 @@ namespace Flurl.Test.Http } [Test, ExpectedException(typeof(FlurlHttpException))] - public async Task can_clear_non_success_status() { + public async Task can_override_settings_fluently() { using (var test = new HttpTest()) { - test.RespondWith(418, "I'm a teapot"); - // allow 4xx - var client = "http://www.api.com".AllowHttpStatus("4xx"); - // but then disallow it - client.Settings.AllowedHttpStatusRange = null; - await client.GetAsync(); - } - } - - [Test] - public async Task can_allow_specific_http_status() { - using (var test = new HttpTest()) { - test.RespondWith(404, "Nothing to see here"); - // allow 404 - var client = "http://www.api.com".AllowHttpStatus(HttpStatusCode.Conflict, HttpStatusCode.NotFound); - await client.DeleteAsync(); + FlurlHttp.GlobalSettings.AllowedHttpStatusRange = "*"; + test.RespondWith(500, "epic fail"); + await "http://www.api.com".ConfigureClient(c => c.AllowedHttpStatusRange = "2xx").GetAsync(); } } } diff --git a/Test/Flurl.Test.Shared/Http/SettingsTests.cs b/Test/Flurl.Test.Shared/Http/GlobalConfigTests.cs similarity index 70% rename from Test/Flurl.Test.Shared/Http/SettingsTests.cs rename to Test/Flurl.Test.Shared/Http/GlobalConfigTests.cs index d5e680a..8444b0b 100644 --- a/Test/Flurl.Test.Shared/Http/SettingsTests.cs +++ b/Test/Flurl.Test.Shared/Http/GlobalConfigTests.cs @@ -1,4 +1,5 @@ using System; +using System.Net; using System.Net.Http; using System.Threading.Tasks; using Flurl.Http; @@ -8,30 +9,41 @@ using NUnit.Framework; namespace Flurl.Test.Http { - [TestFixture] - public class GlobalConfigTests + /// + /// All global settings can also be set at the client level, so this base class allows ClientConfigTests to + /// inherit all the same tests. + /// + public abstract class ConfigTestsBase { + protected abstract FlurlHttpSettings GetSettings(); + + private FlurlClient _client; + protected FlurlClient GetClient() { + if (_client == null) + _client = new FlurlClient("http://www.api.com"); + return _client; + } + [TearDown] public void ResetDefaults() { - FlurlHttp.GlobalSettings.ResetDefaults(); + GetSettings().ResetDefaults(); + _client = null; } [Test] public void can_provide_custom_httpclient_factory() { - FlurlHttp.GlobalSettings.HttpClientFactory = new SomeCustomHttpClientFactory(); - var client = new FlurlClient("http://www.api.com"); - - Assert.IsInstanceOf(client.HttpClient); - Assert.IsInstanceOf(client.HttpMessageHandler); + GetSettings().HttpClientFactory = new SomeCustomHttpClientFactory(); + Assert.IsInstanceOf(GetClient().HttpClient); + Assert.IsInstanceOf(GetClient().HttpMessageHandler); } [Test] public async Task can_allow_non_success_status() { using (var test = new HttpTest()) { - FlurlHttp.GlobalSettings.AllowedHttpStatusRange = "4xx"; + GetSettings().AllowedHttpStatusRange = "4xx"; test.RespondWith(418, "I'm a teapot"); try { - var result = await "http://www.api.com".GetAsync(); + var result = await GetClient().GetAsync(); Assert.IsFalse(result.IsSuccessStatusCode); } catch (Exception) { @@ -45,12 +57,12 @@ namespace Flurl.Test.Http var callbackCalled = false; using (var test = new HttpTest()) { test.RespondWith("ok"); - FlurlHttp.GlobalSettings.BeforeCall = req => { + GetSettings().BeforeCall = req => { CollectionAssert.IsNotEmpty(test.ResponseQueue); // verifies that callback is running before HTTP call is made callbackCalled = true; }; Assert.IsFalse(callbackCalled); - await "http://www.api.com".GetAsync(); + await GetClient().GetAsync(); Assert.IsTrue(callbackCalled); } } @@ -60,12 +72,12 @@ namespace Flurl.Test.Http var callbackCalled = false; using (var test = new HttpTest()) { test.RespondWith("ok"); - FlurlHttp.GlobalSettings.AfterCall = call => { + GetSettings().AfterCall = call => { CollectionAssert.IsEmpty(test.ResponseQueue); // verifies that callback is running after HTTP call is made callbackCalled = true; }; Assert.IsFalse(callbackCalled); - await "http://www.api.com".GetAsync(); + await GetClient().GetAsync(); Assert.IsTrue(callbackCalled); } } @@ -76,14 +88,14 @@ namespace Flurl.Test.Http var callbackCalled = false; using (var test = new HttpTest()) { test.RespondWith(500, "server error"); - FlurlHttp.GlobalSettings.OnError = call => { + GetSettings().OnError = call => { CollectionAssert.IsEmpty(test.ResponseQueue); // verifies that callback is running after HTTP call is made callbackCalled = true; call.ExceptionHandled = markExceptionHandled; }; Assert.IsFalse(callbackCalled); try { - await "http://www.api.com".GetAsync(); + await GetClient().GetAsync(); Assert.IsTrue(callbackCalled, "OnError was never called"); Assert.IsTrue(markExceptionHandled, "ExceptionHandled was marked false in callback, but exception was not propagated."); } @@ -97,12 +109,12 @@ namespace Flurl.Test.Http [Test] public async Task can_disable_exception_behavior() { using (var test = new HttpTest()) { - FlurlHttp.GlobalSettings.OnError = call => { + GetSettings().OnError = call => { call.ExceptionHandled = true; }; test.RespondWith(500, "server error"); try { - var result = await "http://www.api.com".GetAsync(); + var result = await GetClient().GetAsync(); Assert.IsFalse(result.IsSuccessStatusCode); } catch (FlurlHttpException) { @@ -111,18 +123,6 @@ namespace Flurl.Test.Http } } - [Test] - public async Task client_can_override_global_settings() { - var overridden = false; - using (new HttpTest()) { - FlurlHttp.GlobalSettings.AfterCall = _ => overridden = false; - var fc = new FlurlClient("http://www.api.com"); - fc.Settings.AfterCall = _ => overridden = true; - await fc.GetAsync(); - Assert.True(overridden); - } - } - private class SomeCustomHttpClientFactory : IHttpClientFactory { public HttpClient CreateClient(Url url, HttpMessageHandler handler) { @@ -137,4 +137,12 @@ namespace Flurl.Test.Http private class SomeCustomHttpClient : HttpClient { } private class SomeCustomMessageHandler : HttpClientHandler { } } + + [TestFixture] + public class GlobalConfigTestsBase : ConfigTestsBase + { + protected override FlurlHttpSettings GetSettings() { + return FlurlHttp.GlobalSettings; + } + } }