From cc47426c77fd863e74e3d57f201b95392292f48b Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Tue, 1 May 2018 13:04:56 -0700 Subject: [PATCH] Add some initial functional tests for auth samples (#41) --- build/dependencies.props | 1 + .../Views/Account/Signin.cshtml | 4 +- .../AuthSamples.FunctionalTests.csproj | 1 + .../CustomPolicyProviderTests.cs | 87 +++++++++++++- .../HttpClientExtensions.cs | 63 ++++++++++ .../AuthSamples.FunctionalTests/TestAssert.cs | 111 ++++++++++++++++++ 6 files changed, 260 insertions(+), 7 deletions(-) create mode 100644 test/AuthSamples.FunctionalTests/HttpClientExtensions.cs create mode 100644 test/AuthSamples.FunctionalTests/TestAssert.cs diff --git a/build/dependencies.props b/build/dependencies.props index e6ad4bcb69..7244ae274d 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -3,6 +3,7 @@ $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + 0.9.9 2.2.0-preview1-17042 2.2.0-preview1-34066 2.2.0-preview1-34066 diff --git a/samples/CustomPolicyProvider/Views/Account/Signin.cshtml b/samples/CustomPolicyProvider/Views/Account/Signin.cshtml index a245e76195..84dbc2a24d 100644 --- a/samples/CustomPolicyProvider/Views/Account/Signin.cshtml +++ b/samples/CustomPolicyProvider/Views/Account/Signin.cshtml @@ -3,12 +3,12 @@
- +
- +
diff --git a/test/AuthSamples.FunctionalTests/AuthSamples.FunctionalTests.csproj b/test/AuthSamples.FunctionalTests/AuthSamples.FunctionalTests.csproj index 0cb04a92fd..4d50d4e6a7 100644 --- a/test/AuthSamples.FunctionalTests/AuthSamples.FunctionalTests.csproj +++ b/test/AuthSamples.FunctionalTests/AuthSamples.FunctionalTests.csproj @@ -29,5 +29,6 @@ + diff --git a/test/AuthSamples.FunctionalTests/CustomPolicyProviderTests.cs b/test/AuthSamples.FunctionalTests/CustomPolicyProviderTests.cs index 86d6647c45..44eab3973b 100644 --- a/test/AuthSamples.FunctionalTests/CustomPolicyProviderTests.cs +++ b/test/AuthSamples.FunctionalTests/CustomPolicyProviderTests.cs @@ -1,6 +1,8 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Threading.Tasks; @@ -13,7 +15,7 @@ namespace AuthSamples.FunctionalTests { public CustomPolicyProviderTests(WebApplicationFactory fixture) { - Client = fixture.CreateDefaultClient(); + Client = fixture.CreateClient(); } public HttpClient Client { get; } @@ -37,8 +39,70 @@ namespace AuthSamples.FunctionalTests var content = await response.Content.ReadAsStringAsync(); // Assert - Assert.Equal(HttpStatusCode.Redirect, response.StatusCode); - Assert.Equal("http://localhost/account/signin?ReturnUrl=%2FHome%2FMinimumAge10", response.Headers.Location.ToString()); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("http://localhost/account/signin?ReturnUrl=%2FHome%2FMinimumAge10", response.RequestMessage.RequestUri.ToString()); + } + + [Fact] + public async Task MinimumAge10WorksIfOldEnough() + { + // Arrange & Act + var signIn = await SignIn(Client, "Dude", DateTime.Now.Subtract(TimeSpan.FromDays(365 * 20)).ToShortDateString()); + Assert.Equal(HttpStatusCode.OK, signIn.StatusCode); + + var response = await Client.GetAsync("/Home/MinimumAge10"); + var content = await response.Content.ReadAsStringAsync(); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Contains("Welcome, Dude", content); + Assert.Contains("Welcome to a page restricted to users 10 or older", content); + } + + [Fact] + public async Task MinimumAge10FailsIfNotOldEnough() + { + // Arrange & Act + var signIn = await SignIn(Client, "Dude", DateTime.Now.Subtract(TimeSpan.FromDays(365 * 5)).ToShortDateString()); + Assert.Equal(HttpStatusCode.OK, signIn.StatusCode); + + var response = await Client.GetAsync("/Home/MinimumAge10"); + var content = await response.Content.ReadAsStringAsync(); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Contains("Access Denied: Dude is not authorized to view this page.", content); + } + + [Fact] + public async Task MinimumAge50WorksIfOldEnough() + { + // Arrange & Act + var signIn = await SignIn(Client, "Dude", DateTime.Now.Subtract(TimeSpan.FromDays(365 * 55)).ToShortDateString()); + Assert.Equal(HttpStatusCode.OK, signIn.StatusCode); + + var response = await Client.GetAsync("/Home/MinimumAge50"); + var content = await response.Content.ReadAsStringAsync(); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Contains("Welcome, Dude", content); + Assert.Contains("Welcome to a page restricted to users 50 or older", content); + } + + [Fact] + public async Task MinimumAge50FailsIfNotOldEnough() + { + // Arrange & Act + var signIn = await SignIn(Client, "Dude", DateTime.Now.Subtract(TimeSpan.FromDays(365 * 20)).ToShortDateString()); + Assert.Equal(HttpStatusCode.OK, signIn.StatusCode); + + var response = await Client.GetAsync("/Home/MinimumAge50"); + var content = await response.Content.ReadAsStringAsync(); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Contains("Access Denied: Dude is not authorized to view this page.", content); } [Fact] @@ -49,8 +113,21 @@ namespace AuthSamples.FunctionalTests var content = await response.Content.ReadAsStringAsync(); // Assert - Assert.Equal(HttpStatusCode.Redirect, response.StatusCode); - Assert.Equal("http://localhost/account/signin?ReturnUrl=%2FHome%2FMinimumAge50", response.Headers.Location.ToString()); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("http://localhost/account/signin?ReturnUrl=%2FHome%2FMinimumAge50", response.RequestMessage.RequestUri.ToString()); + } + + internal static async Task SignIn(HttpClient client, string userName, string dob) + { + var goToSignIn = await client.GetAsync("/account/signin"); + var signIn = await TestAssert.IsHtmlDocumentAsync(goToSignIn); + + var form = TestAssert.HasForm(signIn); + return await client.SendAsync(form, new Dictionary() + { + ["UserName"] = userName, + ["DOB"] = dob, + }); } } } diff --git a/test/AuthSamples.FunctionalTests/HttpClientExtensions.cs b/test/AuthSamples.FunctionalTests/HttpClientExtensions.cs new file mode 100644 index 0000000000..b83dfe00de --- /dev/null +++ b/test/AuthSamples.FunctionalTests/HttpClientExtensions.cs @@ -0,0 +1,63 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using AngleSharp.Dom.Html; +using Xunit; + +namespace AuthSamples.FunctionalTests +{ + public static class HttpClientExtensions + { + // Copied from Identity functionals + public static Task SendAsync( + this HttpClient client, + IHtmlFormElement form, + IEnumerable> formValues) + { + var submitElement = Assert.Single(form.QuerySelectorAll("[type=submit]")); + var submitButton = Assert.IsAssignableFrom(submitElement); + + return client.SendAsync(form, submitButton, formValues); + } + + // Copied from Identity functionals + public static Task SendAsync( + this HttpClient client, + IHtmlFormElement form, + IHtmlElement submitButton, + IEnumerable> formValues) + { + foreach (var kvp in formValues) + { + var element = Assert.IsAssignableFrom(form[kvp.Key]); + element.Value = kvp.Value; + } + + var submit = form.GetSubmission(submitButton); + var target = (Uri)submit.Target; + if (submitButton.HasAttribute("formaction")) + { + var formaction = submitButton.GetAttribute("formaction"); + target = new Uri(formaction, UriKind.Relative); + } + var submision = new HttpRequestMessage(new HttpMethod(submit.Method.ToString()), target) + { + Content = new StreamContent(submit.Body) + }; + + foreach (var header in submit.Headers) + { + if (!submision.Headers.TryAddWithoutValidation(header.Key, header.Value)) + { + submision.Content.Headers.TryAddWithoutValidation(header.Key, header.Value); + } + } + + return client.SendAsync(submision); + } + } +} diff --git a/test/AuthSamples.FunctionalTests/TestAssert.cs b/test/AuthSamples.FunctionalTests/TestAssert.cs new file mode 100644 index 0000000000..6e884e3d69 --- /dev/null +++ b/test/AuthSamples.FunctionalTests/TestAssert.cs @@ -0,0 +1,111 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading; +using System.Threading.Tasks; +using AngleSharp; +using AngleSharp.Dom; +using AngleSharp.Dom.Html; +using AngleSharp.Network; +using AngleSharp.Parser.Html; +using Xunit; + +namespace AuthSamples.FunctionalTests +{ + // Merged HtmlAssert + ResponseAssert from Identity functional tests + public class TestAssert + { + public static IHtmlFormElement HasForm(IHtmlDocument document) + { + var form = Assert.Single(document.QuerySelectorAll("form")); + return Assert.IsAssignableFrom(form); + } + + public static IHtmlAnchorElement HasLink(string selector, IHtmlDocument document) + { + var element = Assert.Single(document.QuerySelectorAll(selector)); + return Assert.IsAssignableFrom(element); + } + + internal static IEnumerable HasElements(string selector, IHtmlDocument document) + { + var elements = document + .QuerySelectorAll(selector) + .OfType() + .ToArray(); + + Assert.NotEmpty(elements); + + return elements; + } + + public static IHtmlElement HasElement(string selector, IParentNode document) + { + var element = Assert.Single(document.QuerySelectorAll(selector)); + return Assert.IsAssignableFrom(element); + } + + public static IHtmlFormElement HasForm(string selector, IParentNode document) + { + var form = Assert.Single(document.QuerySelectorAll(selector)); + return Assert.IsAssignableFrom(form); + } + + internal static IHtmlHtmlElement IsHtmlFragment(string htmlMessage) + { + var synteticNode = $"
{htmlMessage}
"; + var fragment = Assert.Single(new HtmlParser().ParseFragment(htmlMessage, context: null)); + return Assert.IsAssignableFrom(fragment); + } + + public static Uri IsRedirect(HttpResponseMessage responseMessage) + { + Assert.Equal(HttpStatusCode.Redirect, responseMessage.StatusCode); + return responseMessage.Headers.Location; + } + + public static async Task IsHtmlDocumentAsync(HttpResponseMessage response) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("text/html", response.Content.Headers.ContentType.MediaType); + var content = await response.Content.ReadAsStringAsync(); + var document = await BrowsingContext.New() + .OpenAsync(ResponseFactory, CancellationToken.None); + return Assert.IsAssignableFrom(document); + + void ResponseFactory(VirtualResponse htmlResponse) + { + htmlResponse + .Address(response.RequestMessage.RequestUri) + .Status(response.StatusCode); + + MapHeaders(response.Headers); + MapHeaders(response.Content.Headers); + + htmlResponse.Content(content); + + void MapHeaders(HttpHeaders headers) + { + foreach (var header in headers) + { + foreach (var value in header.Value) + { + htmlResponse.Header(header.Key, value); + } + } + } + } + } + + internal static void IsOK(HttpResponseMessage download) + { + Assert.Equal(HttpStatusCode.OK, download.StatusCode); + } + } +}