Skip to content

Commit

Permalink
cookies, headers, and new collection types (#541)
Browse files Browse the repository at this point in the history
  • Loading branch information
tmenier committed Aug 7, 2020
1 parent b265823 commit 5f59f4f
Show file tree
Hide file tree
Showing 24 changed files with 459 additions and 306 deletions.
4 changes: 2 additions & 2 deletions Test/Flurl.Test/Flurl.Test.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net45;netcoreapp2.0;</TargetFrameworks>
<TargetFrameworks>net471;netcoreapp2.0;</TargetFrameworks>
<TargetFrameworks Condition="'$(OS)' != 'Windows_NT'">netcoreapp2.0</TargetFrameworks>
</PropertyGroup>

Expand All @@ -19,7 +19,7 @@
<ProjectReference Include="..\..\src\Flurl.Http\Flurl.Http.csproj" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net45'">
<ItemGroup Condition="'$(TargetFramework)' == 'net471'">
<Reference Include="System.Net.Http" />
<Reference Include="Microsoft.CSharp" />
</ItemGroup>
Expand Down
117 changes: 61 additions & 56 deletions Test/Flurl.Test/Http/CookieTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ await cli.Request().GetAsync()
HttpTest.ShouldHaveMadeACall().WithCookies(new { y = "bar", z = "fizz" }).Times(1);
HttpTest.ShouldHaveMadeACall().WithoutCookies().Times(1);

Assert.AreEqual("bar", responses[0].Cookies.TryGetValue("x", out var c) ? c.Value : null);
Assert.AreEqual("bar", responses[0].Cookies.FirstOrDefault(c => c.Name == "x")?.Value);
Assert.IsEmpty(responses[1].Cookies);
Assert.IsEmpty(responses[2].Cookies);
}
Expand Down Expand Up @@ -57,8 +57,8 @@ public async Task can_send_and_receive_cookies_with_jar() {
HttpTest.ShouldHaveMadeACall().WithCookies(new { x = "foo", y = "bazz" }).Times(1);

Assert.AreEqual(2, jar.Count);
Assert.AreEqual("foo", jar["x"].Value);
Assert.AreEqual("bazz", jar["y"].Value);
Assert.AreEqual(1, jar.Count(c => c.Name == "x" && c.Value == "foo"));
Assert.AreEqual(1, jar.Count(c => c.Name == "y" && c.Value == "bazz"));
}

[Test]
Expand All @@ -82,8 +82,8 @@ public async Task can_send_and_receive_cookies_with_jar_initialized() {
HttpTest.ShouldHaveMadeACall().WithCookies(new { x = "foo", y = "bazz" }).Times(1);

Assert.AreEqual(2, jar.Count);
Assert.AreEqual("foo", jar["x"].Value);
Assert.AreEqual("bazz", jar["y"].Value);
Assert.AreEqual(1, jar.Count(c => c.Name == "x" && c.Value == "foo"));
Assert.AreEqual(1, jar.Count(c => c.Name == "y" && c.Value == "bazz"));
}

[Test]
Expand All @@ -106,15 +106,15 @@ public async Task can_do_cookie_session() {
HttpTest.ShouldHaveMadeACall().WithCookies(new { x = "foo", y = "bazz" }).Times(1);

Assert.AreEqual(2, cs.Cookies.Count);
Assert.AreEqual("foo", cs.Cookies["x"].Value);
Assert.AreEqual("bazz", cs.Cookies["y"].Value);
Assert.AreEqual(1, cs.Cookies.Count(c => c.Name == "x" && c.Value == "foo"));
Assert.AreEqual(1, cs.Cookies.Count(c => c.Name == "y" && c.Value == "bazz"));
}
}

[Test]
public void can_parse_set_cookie_header() {
var start = DateTimeOffset.UtcNow;
var cookie = CookieCutter.FromResponseHeader("https://www.cookies.com/a/b", "x=foo ; DoMaIn=cookies.com ; path=/ ; MAX-AGE=999 ; expires= ; secure ;HTTPONLY ;samesite=none");
var cookie = CookieCutter.ParseResponseHeader("https://www.cookies.com/a/b", "x=foo ; DoMaIn=cookies.com ; path=/ ; MAX-AGE=999 ; expires= ; secure ;HTTPONLY ;samesite=none");
Assert.AreEqual("https://www.cookies.com/a/b", cookie.OriginUrl.ToString());
Assert.AreEqual("x", cookie.Name);
Assert.AreEqual("foo", cookie.Value);
Expand All @@ -130,7 +130,7 @@ public void can_parse_set_cookie_header() {

// simpler case
start = DateTimeOffset.UtcNow;
cookie = CookieCutter.FromResponseHeader("https://www.cookies.com/a/b", "y=bar");
cookie = CookieCutter.ParseResponseHeader("https://www.cookies.com/a/b", "y=bar");
Assert.AreEqual("https://www.cookies.com/a/b", cookie.OriginUrl.ToString());
Assert.AreEqual("y", cookie.Name);
Assert.AreEqual("bar", cookie.Value);
Expand All @@ -145,68 +145,79 @@ public void can_parse_set_cookie_header() {
Assert.LessOrEqual(cookie.DateReceived, DateTimeOffset.UtcNow);
}

[Test]
public void cannot_change_cookie_after_adding_to_jar() {
var cookie = new FlurlCookie("x", "foo", "https://cookies.com");

// good
cookie.Value = "value2";
cookie.Path = "/";
cookie.Secure = true;

var jar = new CookieJar().AddOrUpdate(cookie);

// bad
Assert.Throws<Exception>(() => cookie.Value = "value3");
Assert.Throws<Exception>(() => cookie.Path = "/a");
Assert.Throws<Exception>(() => cookie.Secure = false);
}

[Test]
public void url_decodes_cookie_value() {
var cookie = CookieCutter.FromResponseHeader("https://cookies.com", "x=one%3A%20for%20the%20money");
var cookie = CookieCutter.ParseResponseHeader("https://cookies.com", "x=one%3A%20for%20the%20money");
Assert.AreEqual("one: for the money", cookie.Value);
}

[Test]
public void unquotes_cookie_value() {
var cookie = CookieCutter.FromResponseHeader("https://cookies.com", "x=\"hello there\"" );
var cookie = CookieCutter.ParseResponseHeader("https://cookies.com", "x=\"hello there\"" );
Assert.AreEqual("hello there", cookie.Value);
}

[Test]
public void jar_syncs_to_request_cookies() {
var jar = new CookieJar().AddOrUpdate("x", "foo", "https://cookies.com");
public void jar_overwrites_request_cookies() {
var jar = new CookieJar()
.AddOrUpdate("b", 10, "https://cookies.com")
.AddOrUpdate("c", 11, "https://cookies.com");

var req = new FlurlRequest("http://cookies.com").WithCookies(jar);
Assert.IsTrue(req.Cookies.ContainsKey("x"));
Assert.AreEqual("foo", req.Cookies["x"]);
var req = new FlurlRequest("http://cookies.com")
.WithCookies(new { a = 1, b = 2 })
.WithCookies(jar);

jar["x"].Value = "new val!";
Assert.AreEqual("new val!", req.Cookies["x"]);
Assert.AreEqual(3, req.Cookies.Count());
Assert.IsTrue(req.Cookies.Contains(("a", "1")));
Assert.IsTrue(req.Cookies.Contains(("b", "10"))); // the important one
Assert.IsTrue(req.Cookies.Contains(("c", "11")));
}

jar.AddOrUpdate("y", "bar", "https://cookies.com");
Assert.IsTrue(req.Cookies.ContainsKey("y"));
Assert.AreEqual("bar", req.Cookies["y"]);
[Test]
public async Task request_cookies_do_not_overwrite_jar() {
var jar = new CookieJar()
.AddOrUpdate("b", 10, "https://cookies.com")
.AddOrUpdate("c", 11, "https://cookies.com");

jar["x"].Secure = true;
Assert.IsFalse(req.Cookies.ContainsKey("x"));
var req = new FlurlRequest("http://cookies.com")
.WithCookies(jar)
.WithCookies(new { a = 1, b = 2 });

jar.Clear();
Assert.IsFalse(req.Cookies.Any());
}
Assert.AreEqual(3, req.Cookies.Count());
Assert.IsTrue(req.Cookies.Contains(("a", "1")));
Assert.IsTrue(req.Cookies.Contains(("b", "2"))); // value overwritten but just for this request
Assert.IsTrue(req.Cookies.Contains(("c", "11")));

[Test]
public async Task request_cookies_do_not_sync_to_jar() {
var jar = new CookieJar().AddOrUpdate("x", "foo", "https://cookies.com");

var req = new FlurlRequest("http://cookies.com").WithCookies(jar);
Assert.IsTrue(req.Cookies.ContainsKey("x"));
Assert.AreEqual("foo", req.Cookies["x"]);

// changing cookie at request level shouldn't touch jar
req.Cookies["x"] = "bar";
Assert.AreEqual("foo", jar["x"].Value);

await req.GetAsync();
HttpTest.ShouldHaveMadeACall().WithCookies(new { x = "bar" });

// re-check after send
Assert.AreEqual("foo", jar["x"].Value);
// b in jar wasn't touched
Assert.AreEqual("10", jar.FirstOrDefault(c => c.Name == "b")?.Value);
}

[Test]
public void request_cookies_sync_with_cookie_header() {
var req = new FlurlRequest("http://cookies.com").WithCookie("x", "foo");
Assert.AreEqual("x=foo", req.Headers.TryGetValue("Cookie", out var val) ? val : null);
Assert.AreEqual("x=foo", req.Headers.FirstOrDefault("Cookie"));

// should flow from CookieJar too
var jar = new CookieJar().AddOrUpdate("y", "bar", "http://cookies.com");
req = new FlurlRequest("http://cookies.com").WithCookies(jar);
Assert.AreEqual("y=bar", req.Headers.TryGetValue("Cookie", out val) ? val : null);
Assert.AreEqual("y=bar", req.Headers.FirstOrDefault("Cookie"));
}

[TestCase("https://domain1.com", "https://domain1.com", true)]
Expand Down Expand Up @@ -321,7 +332,7 @@ public async Task invalid_cookie_in_response_doesnt_throw() {
Assert.IsEmpty(jar);
// even though the CookieJar rejected the cookie, it doesn't change the fact
// that it exists in the response.
Assert.AreEqual("foo", resp.Cookies.TryGetValue("x", out var c) ? c.Value : null);
Assert.AreEqual("foo", resp.Cookies.FirstOrDefault(c => c.Name == "x")?.Value);
}

/// <summary>
Expand All @@ -336,25 +347,19 @@ private void AssertCookie(FlurlCookie cookie, bool isValid, bool isExpired, stri
Assert.AreEqual(shouldAddToJar, jar.TryAddOrUpdate(cookie, out reason));

if (shouldAddToJar)
CollectionAssert.Contains(jar.Keys, cookie.Name);
Assert.AreEqual(cookie.Name, jar.SingleOrDefault()?.Name);
else {
Assert.Throws<InvalidCookieException>(() => jar.AddOrUpdate(cookie));
CollectionAssert.DoesNotContain(jar.Keys, cookie.Name);
CollectionAssert.IsEmpty(jar);
}

var req = cookie.OriginUrl.WithCookies(jar);
if (shouldAddToJar)
CollectionAssert.Contains(req.Cookies.Keys, cookie.Name);
else
CollectionAssert.DoesNotContain(req.Cookies.Keys, cookie.Name);
Assert.AreEqual(shouldAddToJar, req.Cookies.Contains((cookie.Name, cookie.Value)));

if (requestUrl != null) {
Assert.AreEqual(shouldSend, cookie.ShouldSendTo(requestUrl, out reason), reason);
req = requestUrl.WithCookies(jar);
if (shouldSend)
CollectionAssert.Contains(req.Cookies.Keys, cookie.Name);
else
CollectionAssert.DoesNotContain(req.Cookies.Keys, cookie.Name);
Assert.AreEqual(shouldSend, req.Cookies.Contains((cookie.Name, cookie.Value)));
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion Test/Flurl.Test/Http/GetTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public async Task can_get_response_then_deserialize() {

var resp = await "http://some-api.com".GetAsync();
Assert.AreEqual(234, resp.StatusCode);
Assert.IsTrue(resp.Headers.TryGetValue("my-header", out var headerVal));
Assert.IsTrue(resp.Headers.TryGetFirst("my-header", out var headerVal));
Assert.AreEqual("hi", headerVal);

var data = await resp.GetJsonAsync<TestData>();
Expand Down
14 changes: 8 additions & 6 deletions Test/Flurl.Test/Http/RealHttpTests.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Flurl.Http;
Expand Down Expand Up @@ -289,8 +289,9 @@ public async Task can_allow_real_http_in_test() {
[Test]
public async Task can_send_cookies() {
var req = "https://httpbin.org/cookies".WithCookies(new { x = 1, y = 2 });
Assert.AreEqual("1", req.Cookies["x"]);
Assert.AreEqual("2", req.Cookies["y"]);
Assert.AreEqual(2, req.Cookies.Count());
Assert.IsTrue(req.Cookies.Contains(("x", "1")));
Assert.IsTrue(req.Cookies.Contains(("y", "2")));

var s = await req.GetStringAsync();

Expand All @@ -304,16 +305,17 @@ public async Task can_send_cookies() {
public async Task can_receive_cookies() {
// endpoint does a redirect, so we need to disable auto-redirect in order to see the cookie in the response
var resp = await "https://httpbin.org/cookies/set?z=999".WithAutoRedirect(false).GetAsync();
Assert.AreEqual("999", resp.Cookies["z"].Value);
Assert.AreEqual("999", resp.Cookies.FirstOrDefault(c => c.Name == "z")?.Value);


// but using WithCookies we can capture it even with redirects enabled
await "https://httpbin.org/cookies/set?z=999".WithCookies(out var cookies).GetAsync();
Assert.AreEqual("999", cookies["z"].Value);
Assert.AreEqual("999", cookies.FirstOrDefault(c => c.Name == "z")?.Value);

// this works with redirects too
using (var session = new CookieSession("https://httpbin.org/cookies")) {
await session.Request("set?z=999").GetAsync();
Assert.AreEqual("999", session.Cookies["z"].Value);
Assert.AreEqual("999", session.Cookies.FirstOrDefault(c => c.Name == "z")?.Value);
}
}

Expand Down
27 changes: 13 additions & 14 deletions Test/Flurl.Test/Http/SettingsExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,16 @@ public void can_set_timeout_in_seconds() {
[Test]
public void can_set_header() {
var sc = GetSettingsContainer().WithHeader("a", 1);
Assert.AreEqual(1, sc.Headers.Count);
Assert.AreEqual(1, sc.Headers["a"]);
Assert.AreEqual(("a", 1), sc.Headers.Single());
}

[Test]
public void can_set_headers_from_anon_object() {
// null values shouldn't be added
var sc = GetSettingsContainer().WithHeaders(new { a = "b", one = 2, three = (object)null });
Assert.AreEqual(2, sc.Headers.Count);
Assert.AreEqual("b", sc.Headers["a"]);
Assert.AreEqual(2, sc.Headers["one"]);
Assert.IsTrue(sc.Headers.Contains("a", "b"));
Assert.IsTrue(sc.Headers.Contains("one", 2));
}

[Test]
Expand All @@ -51,48 +50,48 @@ public void can_remove_header_by_setting_null() {
Assert.AreEqual(2, sc.Headers.Count);
sc.WithHeader("b", null);
Assert.AreEqual(1, sc.Headers.Count);
Assert.AreEqual("a", sc.Headers.Keys.Single());
Assert.IsFalse(sc.Headers.Contains("b"));
}

[Test]
public void can_set_headers_from_dictionary() {
var sc = GetSettingsContainer().WithHeaders(new Dictionary<string, object> { { "a", "b" }, { "one", 2 } });
Assert.AreEqual(2, sc.Headers.Count);
Assert.AreEqual("b", sc.Headers["a"]);
Assert.AreEqual(2, sc.Headers["one"]);
Assert.IsTrue(sc.Headers.Contains("a", "b"));
Assert.IsTrue(sc.Headers.Contains("one", 2));
}

[Test]
public void underscores_in_properties_convert_to_hyphens_in_header_names() {
var sc = GetSettingsContainer().WithHeaders(new { User_Agent = "Flurl", Cache_Control = "no-cache" });
Assert.IsTrue(sc.Headers.ContainsKey("User-Agent"));
Assert.IsTrue(sc.Headers.ContainsKey("Cache-Control"));
Assert.IsTrue(sc.Headers.Contains("User-Agent"));
Assert.IsTrue(sc.Headers.Contains("Cache-Control"));

// make sure we can disable the behavior
sc.WithHeaders(new { no_i_really_want_underscores = "foo" }, false);
Assert.IsTrue(sc.Headers.ContainsKey("no_i_really_want_underscores"));
Assert.IsTrue(sc.Headers.Contains("no_i_really_want_underscores"));

// dictionaries don't get this behavior since you can use hyphens explicitly
sc.WithHeaders(new Dictionary<string, string> { { "exclude_dictionaries", "bar" } });
Assert.IsTrue(sc.Headers.ContainsKey("exclude_dictionaries"));
Assert.IsTrue(sc.Headers.Contains("exclude_dictionaries"));

// same with strings
sc.WithHeaders("exclude_strings=123");
Assert.IsTrue(sc.Headers.ContainsKey("exclude_strings"));
Assert.IsTrue(sc.Headers.Contains("exclude_strings"));
}

[Test]
public void can_setup_oauth_bearer_token() {
var sc = GetSettingsContainer().WithOAuthBearerToken("mytoken");
Assert.AreEqual(1, sc.Headers.Count);
Assert.AreEqual("Bearer mytoken", sc.Headers["Authorization"]);
Assert.IsTrue(sc.Headers.Contains("Authorization", "Bearer mytoken"));
}

[Test]
public void can_setup_basic_auth() {
var sc = GetSettingsContainer().WithBasicAuth("user", "pass");
Assert.AreEqual(1, sc.Headers.Count);
Assert.AreEqual("Basic dXNlcjpwYXNz", sc.Headers["Authorization"]);
Assert.IsTrue(sc.Headers.Contains("Authorization", "Basic dXNlcjpwYXNz"));
}

[Test]
Expand Down
8 changes: 3 additions & 5 deletions Test/Flurl.Test/Http/TestingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -352,9 +352,7 @@ public async Task can_fake_headers() {
HttpTest.RespondWith(headers: new { h1 = "foo" });

var resp = await "http://www.api.com".GetAsync();
Assert.AreEqual(1, resp.Headers.Count());
Assert.AreEqual("h1", resp.Headers.First().Key);
Assert.AreEqual("foo", resp.Headers.First().Value);
Assert.AreEqual(("h1", "foo"), resp.Headers.Single());
}

[Test]
Expand All @@ -363,7 +361,7 @@ public async Task can_fake_cookies() {

var resp = await "http://www.api.com".GetAsync();
Assert.AreEqual(1, resp.Cookies.Count);
Assert.AreEqual("foo", resp.Cookies["c1"].Value);
Assert.AreEqual("foo", resp.Cookies.FirstOrDefault(c => c.Name == "c1")?.Value);
}

// https://github.com/tmenier/Flurl/issues/175
Expand Down Expand Up @@ -423,7 +421,7 @@ public async Task does_not_throw_nullref_for_empty_content() {
public async Task can_fake_content_headers() {
HttpTest.RespondWith("<xml></xml>", 200, new { Content_Type = "text/xml" });
await "http://api.com".GetAsync();
HttpTest.ShouldHaveMadeACall().With(call => call.Response.Headers.Any(kv => kv.Key == "Content-Type" && kv.Value == "text/xml"));
HttpTest.ShouldHaveMadeACall().With(call => call.Response.Headers.Contains(("Content-Type", "text/xml")));
HttpTest.ShouldHaveMadeACall().With(call => call.HttpResponseMessage.Content.Headers.ContentType.MediaType == "text/xml");
}

Expand Down
Loading

0 comments on commit 5f59f4f

Please sign in to comment.