From 4724c709dc40d9b94ffe7c4ae8cd45f7e92fb4ba Mon Sep 17 00:00:00 2001 From: Todd Date: Sat, 28 Mar 2020 13:48:19 -0500 Subject: [PATCH] #508 more test asserts for headers, cookies --- Test/Flurl.Test/Http/CookieTests.cs | 17 ++- src/Flurl.Http/Testing/HttpCallAssertion.cs | 126 ++++++++++++++++---- src/Flurl.Http/Testing/Util.cs | 58 ++++----- 3 files changed, 138 insertions(+), 63 deletions(-) diff --git a/Test/Flurl.Test/Http/CookieTests.cs b/Test/Flurl.Test/Http/CookieTests.cs index 6fb98ee..f469fa8 100644 --- a/Test/Flurl.Test/Http/CookieTests.cs +++ b/Test/Flurl.Test/Http/CookieTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -24,9 +24,8 @@ namespace Flurl.Test.Http await "https://cookies.com/2".WithCookies(cookies).GetAsync(); await "https://cookies.com/3".WithCookies(cookies).GetAsync(); - HttpTest.ShouldHaveMadeACall().WithCookie("x", "foo").Times(3); - HttpTest.ShouldHaveMadeACall().WithCookie("y", "bar").Times(2); - HttpTest.ShouldHaveMadeACall().WithCookie("y", "bazz").Times(1); + HttpTest.ShouldHaveMadeACall().WithCookies(new { x = "foo", y = "bar" }).Times(2); + HttpTest.ShouldHaveMadeACall().WithCookies(new { x = "foo", y = "bazz" }).Times(1); Assert.AreEqual(2, cookies.Count); Assert.AreEqual("foo", cookies["x"].Value); @@ -47,9 +46,8 @@ namespace Flurl.Test.Http await "https://cookies.com/2".WithCookies(cookies).GetAsync(); await "https://cookies.com/3".WithCookies(cookies).GetAsync(); - HttpTest.ShouldHaveMadeACall().WithCookie("x", "foo").Times(4); - HttpTest.ShouldHaveMadeACall().WithCookie("y", "bar").Times(3); - HttpTest.ShouldHaveMadeACall().WithCookie("y", "bazz").Times(1); + HttpTest.ShouldHaveMadeACall().WithCookies(new { x = "foo", y = "bar" }).Times(3); + HttpTest.ShouldHaveMadeACall().WithCookies(new { x = "foo", y = "bazz" }).Times(1); Assert.AreEqual(2, cookies.Count); Assert.AreEqual("foo", cookies["x"].Value); @@ -71,9 +69,8 @@ namespace Flurl.Test.Http await cs.Request("2").GetAsync(); await cs.Request("3").GetAsync(); - HttpTest.ShouldHaveMadeACall().WithCookie("x", "foo").Times(3); - HttpTest.ShouldHaveMadeACall().WithCookie("y", "bar").Times(2); - HttpTest.ShouldHaveMadeACall().WithCookie("y", "bazz").Times(1); + HttpTest.ShouldHaveMadeACall().WithCookies(new { x = "foo", y = "bar" }).Times(2); + HttpTest.ShouldHaveMadeACall().WithCookies(new { x = "foo", y = "bazz" }).Times(1); Assert.AreEqual(2, cs.Cookies.Count); Assert.AreEqual("foo", cs.Cookies["x"].Value); diff --git a/src/Flurl.Http/Testing/HttpCallAssertion.cs b/src/Flurl.Http/Testing/HttpCallAssertion.cs index 5718205..aef9254 100644 --- a/src/Flurl.Http/Testing/HttpCallAssertion.cs +++ b/src/Flurl.Http/Testing/HttpCallAssertion.cs @@ -114,14 +114,14 @@ namespace Flurl.Http.Testing #region query params /// - /// Asserts whether calls were made containing the given query parameter name and (optionally) value. + /// Asserts whether calls were made containing the given query parameter name and (optionally) value. value may contain * wildcard. /// public HttpCallAssertion WithQueryParam(string name, object value = null) { return With(c => c.HasQueryParam(name, value), BuildDescrip("query param", name, value)); } /// - /// Asserts whether calls were made NOT containing the given query parameter and (optionally) value. + /// Asserts whether calls were made NOT containing the given query parameter and (optionally) value. value may contain * wildcard. /// public HttpCallAssertion WithoutQueryParam(string name, object value = null) { return Without(c => c.HasQueryParam(name, value), BuildDescrip("no query param", name, value)); @@ -172,39 +172,123 @@ namespace Flurl.Http.Testing #region headers /// - /// Asserts whether calls were made containing the given request header and (optionally) value. - /// value may contain * wildcard. + /// Asserts whether calls were made containing the given header name and (optionally) value. value may contain * wildcard. /// - public HttpCallAssertion WithHeader(string name, string valuePattern = "*", string descrip = null) { - descrip = descrip ?? BuildDescrip("header", name, valuePattern); - return With(c => c.HasHeader(name, valuePattern), descrip); + public HttpCallAssertion WithHeader(string name, object value = null) { + return With(c => c.HasHeader(name, value), BuildDescrip("header", name, value)); } /// - /// Asserts whether calls were made that do NOT contain the given request header and (optionally) value. - /// value may contain * wildcard. + /// Asserts whether calls were made NOT containing the given header and (optionally) value. value may contain * wildcard. /// - public HttpCallAssertion WithoutHeader(string name, string valuePattern = "*") { - return Without(c => c.HasHeader(name, valuePattern), BuildDescrip("no header", name, valuePattern)); + public HttpCallAssertion WithoutHeader(string name, object value = null) { + return Without(c => c.HasHeader(name, value), BuildDescrip("no header", name, value)); + } + + /// + /// Asserts whether calls were made containing ALL the given headers (regardless of their values). + /// + public HttpCallAssertion WithHeaders(params string[] names) { + return names.Select(n => WithHeader(n)).LastOrDefault() ?? this; + } + + /// + /// Asserts whether calls were made NOT containing any of the given headers. + /// If no names are provided, asserts no calls were made with any headers. + /// + public HttpCallAssertion WithoutHeaders(params string[] names) { + if (!names.Any()) + return With(c => !c.Request.Headers.Any(), "no headers"); + return names.Select(n => WithoutHeader(n)).LastOrDefault() ?? this; + } + + /// + /// Asserts whether calls were made containing ANY the given headers (regardless of their values). + /// If no names are provided, asserts that calls were made containing at least one header with any name. + /// + public HttpCallAssertion WithAnyHeader(params string[] names) { + var descrip = $"any header {string.Join(", ", names)}".Trim(); + return With(call => { + if (!names.Any()) return call.Request.Headers.Any(); + return call.Request.Headers.Select(h => h.Key).Intersect(names).Any(); + }, descrip); + } + + /// + /// Asserts whether calls were made containing all of the given header values. + /// + /// Object (usually anonymous) or dictionary that is parsed to name/value headers to check for. Values may contain * wildcard. + public HttpCallAssertion WithHeaders(object values) { + return values.ToKeyValuePairs().Select(kv => WithHeader(kv.Key, kv.Value)).LastOrDefault() ?? this; + } + + /// + /// Asserts whether calls were made NOT containing any of the given header values. + /// + /// Object (usually anonymous) or dictionary that is parsed to name/value headers to check for. Values may contain * wildcard. + public HttpCallAssertion WithoutHeaders(object values) { + return values.ToKeyValuePairs().Select(kv => WithoutHeader(kv.Key, kv.Value)).LastOrDefault() ?? this; } #endregion #region cookies /// - /// Asserts whether calls were made containing the given cookie and (optionally) value. - /// value may contain * wildcard. + /// Asserts whether calls were made containing the given cookie name and (optionally) value. value may contain * wildcard. /// - public HttpCallAssertion WithCookie(string name, string valuePattern = "*", string descrip = null) { - descrip = descrip ?? BuildDescrip("cookie", name, valuePattern); - return With(c => c.HasCookie(name, valuePattern), descrip); + public HttpCallAssertion WithCookie(string name, object value = null) { + return With(c => c.HasCookie(name, value), BuildDescrip("cookie", name, value)); } /// - /// Asserts whether calls were made that do NOT contain the given cookie and (optionally) value. - /// value may contain * wildcard. + /// Asserts whether calls were made NOT containing the given cookie and (optionally) value. value may contain * wildcard. /// - public HttpCallAssertion WithoutCookie(string name, string valuePattern = "*") { - return Without(c => c.HasCookie(name, valuePattern), BuildDescrip("no cookie", name, valuePattern)); + public HttpCallAssertion WithoutCookie(string name, object value = null) { + return Without(c => c.HasCookie(name, value), BuildDescrip("no cookie", name, value)); + } + + /// + /// Asserts whether calls were made containing ALL the given cookies (regardless of their values). + /// + public HttpCallAssertion WithCookies(params string[] names) { + return names.Select(n => WithCookie(n)).LastOrDefault() ?? this; + } + + /// + /// Asserts whether calls were made NOT containing any of the given cookies. + /// If no names are provided, asserts no calls were made with any cookies. + /// + public HttpCallAssertion WithoutCookies(params string[] names) { + if (!names.Any()) + return With(c => !c.Request.Cookies.Any(), "no cookies"); + return names.Select(n => WithoutCookie(n)).LastOrDefault() ?? this; + } + + /// + /// Asserts whether calls were made containing ANY the given cookies (regardless of their values). + /// If no names are provided, asserts that calls were made containing at least one cookie with any name. + /// + public HttpCallAssertion WithAnyCookie(params string[] names) { + var descrip = $"any cookie {string.Join(", ", names)}".Trim(); + return With(call => { + if (!names.Any()) return call.Request.Cookies.Any(); + return call.Request.Cookies.Select(c => c.Key).Intersect(names).Any(); + }, descrip); + } + + /// + /// Asserts whether calls were made containing all of the given cookie values. + /// + /// Object (usually anonymous) or dictionary that is parsed to name/value cookies to check for. Values may contain * wildcard. + public HttpCallAssertion WithCookies(object values) { + return values.ToKeyValuePairs().Select(kv => WithCookie(kv.Key, kv.Value)).LastOrDefault() ?? this; + } + + /// + /// Asserts whether calls were made NOT containing any of the given cookie values. + /// + /// Object (usually anonymous) or dictionary that is parsed to name/value cookies to check for. Values may contain * wildcard. + public HttpCallAssertion WithoutCookies(object values) { + return values.ToKeyValuePairs().Select(kv => WithoutCookie(kv.Key, kv.Value)).LastOrDefault() ?? this; } #endregion @@ -224,7 +308,7 @@ namespace Flurl.Http.Testing /// Token can contain * wildcard. /// public HttpCallAssertion WithOAuthBearerToken(string token = "*") { - return WithHeader("Authorization", $"Bearer {token}", "bearer token " + token); + return WithHeader("Authorization", $"Bearer {token}"); } /// diff --git a/src/Flurl.Http/Testing/Util.cs b/src/Flurl.Http/Testing/Util.cs index 363b160..4a95ae4 100644 --- a/src/Flurl.Http/Testing/Util.cs +++ b/src/Flurl.Http/Testing/Util.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Text.RegularExpressions; -using System.Threading.Tasks; using Flurl.Util; namespace Flurl.Http.Testing @@ -14,16 +13,6 @@ namespace Flurl.Http.Testing /// internal static class Util { - internal static bool MatchesPattern(string textToCheck, string pattern) { - // avoid regex'ing in simple cases - if (string.IsNullOrEmpty(pattern) || pattern == "*") return true; - if (string.IsNullOrEmpty(textToCheck)) return false; - if (!pattern.Contains("*")) return textToCheck == pattern; - - var regex = "^" + Regex.Escape(pattern).Replace("\\*", "(.*)") + "$"; - return Regex.IsMatch(textToCheck ?? "", regex); - } - internal static bool HasAnyVerb(this FlurlCall call, HttpMethod[] verbs) { // for good measure, check both FlurlRequest.Verb and HttpRequestMessage.Method return verbs.Any(verb => call.Request.Verb == verb && call.HttpRequestMessage.Method == verb); @@ -44,15 +33,11 @@ namespace Flurl.Http.Testing if (!paramVals.Any()) return false; - if (value == null) - return true; - if (value is string s) - return paramVals.Any(v => MatchesPattern(v, s)); - if (value is IEnumerable en) { + if (!(value is string) && value is IEnumerable en) { var values = en.Cast().Select(o => o.ToInvariantString()).ToList(); return values.Intersect(paramVals).Count() == values.Count; } - return paramVals.Any(v => v == value.ToInvariantString()); + return paramVals.Any(v => MatchesValue(v, value)); } internal static bool HasAllQueryParams(this FlurlCall call, string[] names) { @@ -74,23 +59,32 @@ namespace Flurl.Http.Testing return values.ToKeyValuePairs().All(kv => call.HasQueryParam(kv.Key, kv.Value)); } - internal static bool HasHeader(this FlurlCall call, string name, string valuePattern) { - var val = call.HttpRequestMessage.GetHeaderValue(name); - return val != null && MatchesPattern(val, valuePattern); + internal static bool HasHeader(this FlurlCall call, string name, object value) { + return call.Request.Headers.TryGetValue(name, out var val) && MatchesValue(val?.ToInvariantString(), value); } - internal static bool HasCookie(this FlurlCall call, string name, string valuePattern) { - var headerVal = call.HttpRequestMessage.GetHeaderValue("Cookie"); - if (headerVal == null) return false; - return ( - from kv in headerVal.Split(';') - let parts = kv.SplitOnFirstOccurence("=") - where parts.Length == 2 - let key = parts[0].Trim() - where key == name - let val = parts[1].Trim() - where MatchesPattern(val, valuePattern) - select 1).Any(); + internal static bool HasCookie(this FlurlCall call, string name, object value) { + return call.Request.Cookies.TryGetValue(name, out var val) && MatchesValue(val, value); + } + + private static bool MatchesValue(string valueToMatch, object value) { + if (value == null) + return true; + if (valueToMatch == null) + return false; + if (value is string s) + return MatchesPattern(valueToMatch, s); + return valueToMatch == value.ToInvariantString(); + } + + internal static bool MatchesPattern(string textToCheck, string pattern) { + // avoid regex'ing in simple cases + if (string.IsNullOrEmpty(pattern) || pattern == "*") return true; + if (string.IsNullOrEmpty(textToCheck)) return false; + if (!pattern.Contains("*")) return textToCheck == pattern; + + var regex = "^" + Regex.Escape(pattern).Replace("\\*", "(.*)") + "$"; + return Regex.IsMatch(textToCheck ?? "", regex); } } }