Skip to content

Test all links in templates #8628

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

Merged
merged 11 commits into from
Apr 4, 2019
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
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Description>Invoke Node.js modules at runtime in ASP.NET Core applications.</Description>
<TargetFramework>netcoreapp3.0</TargetFramework>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<RazorLangVersion Condition="'$(SupportPagesAndViews)' != 'True'">3.0</RazorLangVersion>
<AddRazorSupportForMvc Condition="'$(SupportPagesAndViews)' == 'True'">true</AddRazorSupportForMvc>
<RootNamespace Condition="'$(name)' != '$(name{-VALUE-FORMS-}safe_namespace)'">Company.RazorClassLibrary1</RootNamespace>
<AddRazorSupportForMvc Condition="'$(SupportPagesAndViews)' == 'True'">true</AddRazorSupportForMvc>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

</PropertyGroup>

<ItemGroup Condition="'$(SupportPagesAndViews)' == 'True'">
Expand Down
14 changes: 8 additions & 6 deletions src/ProjectTemplates/test/EmptyWebTemplateTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@ public EmptyWebTemplateTest(ProjectFactoryFixture projectFactory, ITestOutputHel

public ITestOutputHelper Output { get; }

[Fact]
public async Task EmptyWebTemplateAsync()
[Theory]
[InlineData(null)]
[InlineData("F#")]
public async Task EmptyWebTemplateAsync(string languageOverride)
{
Project = await ProjectFactory.GetOrCreateProject("empty", Output);
Project = await ProjectFactory.GetOrCreateProject("empty" + (languageOverride == "F#" ? "fsharp" : "csharp"), Output);

var createResult = await Project.RunDotNetNewAsync("web");
var createResult = await Project.RunDotNetNewAsync("web", language: languageOverride);
Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", Project, createResult));

var publishResult = await Project.RunDotNetPublishAsync();
Expand All @@ -43,8 +45,8 @@ public async Task EmptyWebTemplateAsync()
using (var aspNetProcess = Project.StartBuiltProjectAsync())
{
Assert.False(
aspNetProcess.Process.HasExited,
ErrorMessages.GetFailedProcessMessageOrEmpty("Run built project", Project, aspNetProcess.Process));
aspNetProcess.Process.HasExited,
ErrorMessages.GetFailedProcessMessageOrEmpty("Run built project", Project, aspNetProcess.Process));

await aspNetProcess.AssertOk("/");
}
Expand Down
71 changes: 71 additions & 0 deletions src/ProjectTemplates/test/GrpcTemplateTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// 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.Threading.Tasks;
using Templates.Test.Helpers;
using Xunit;
using Xunit.Abstractions;

namespace Templates.Test
{
public class GrpcTemplateTest
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ryanbrandenburg we are going to be changing the template for gRPC pretty significantly since we'll be removing the client. We're going to need to update the test since our PRs will collide.

cc @JamesNK @shirhatti

{
public GrpcTemplateTest(ProjectFactoryFixture projectFactory, ITestOutputHelper output)
{
ProjectFactory = projectFactory;
Output = output;
}

public Project Project { get; set; }

public ProjectFactoryFixture ProjectFactory { get; }
public ITestOutputHelper Output { get; }

[Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/7973")]
public async Task GrpcTemplate()
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a class-library or an executable, if it's the former. Should we test it runs?

/cc: @glennc

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't add that because I don't know what the expected behavior is. Would certainly be reasonable to at least check it doesn't crash instantly and file an issue for someone involved with Grpc to test expectations.

Project = await ProjectFactory.GetOrCreateProject("grpc", Output);

var createResult = await Project.RunDotNetNewAsync("grpc");
Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", Project, createResult));

var publishResult = await Project.RunDotNetPublishAsync();
Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", Project, publishResult));

var buildResult = await Project.RunDotNetBuildAsync();
Assert.True(0 == buildResult.ExitCode, ErrorMessages.GetFailedProcessMessage("build", Project, buildResult));

using (var serverProcess = Project.StartBuiltServerAsync())
{
Assert.False(
serverProcess.Process.HasExited,
ErrorMessages.GetFailedProcessMessageOrEmpty("Run built server", Project, serverProcess.Process));

using (var clientProcess = Project.StartBuiltClientAsync(serverProcess))
{
// Wait for the client to do its thing
await Task.Delay(100);
Assert.False(
clientProcess.Process.HasExited,
ErrorMessages.GetFailedProcessMessageOrEmpty("Run built client", Project, clientProcess.Process));
}
}

using (var aspNetProcess = Project.StartPublishedServerAsync())
{
Assert.False(
aspNetProcess.Process.HasExited,
ErrorMessages.GetFailedProcessMessageOrEmpty("Run published server", Project, aspNetProcess.Process));

using (var clientProcess = Project.StartPublishedClientAsync())
{
// Wait for the client to do its thing
await Task.Delay(100);
Assert.False(
clientProcess.Process.HasExited,
ErrorMessages.GetFailedProcessMessageOrEmpty("Run built client", Project, clientProcess.Process));
}
}
}
}
}
95 changes: 82 additions & 13 deletions src/ProjectTemplates/test/Helpers/AspNetProcess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using AngleSharp.Dom.Html;
using AngleSharp.Parser.Html;
using Microsoft.AspNetCore.Certificates.Generation;
using Microsoft.Extensions.CommandLineUtils;
using OpenQA.Selenium;
Expand All @@ -21,17 +23,19 @@ namespace Templates.Test.Helpers
public class AspNetProcess : IDisposable
{
private const string ListeningMessagePrefix = "Now listening on: ";
private readonly Uri _listeningUri;
private readonly HttpClient _httpClient;
private readonly ITestOutputHelper _output;

internal readonly Uri ListeningUri;
internal ProcessEx Process { get; }

public AspNetProcess(
ITestOutputHelper output,
string workingDirectory,
string dllPath,
IDictionary<string, string> environmentVariables)
IDictionary<string, string> environmentVariables,
bool published = true,
bool hasListeningUri = true)
{
_output = output;
_httpClient = new HttpClient(new HttpClientHandler()
Expand All @@ -48,18 +52,20 @@ public AspNetProcess(
var now = DateTimeOffset.Now;
new CertificateManager().EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1));


output.WriteLine("Running ASP.NET application...");

Process = ProcessEx.Run(output, workingDirectory, DotNetMuxer.MuxerPathOrDefault(), $"exec {dllPath}", envVars: environmentVariables);
_listeningUri = GetListeningUri(output);
var arguments = published ? $"exec {dllPath}" : "run";
Process = ProcessEx.Run(output, workingDirectory, DotNetMuxer.MuxerPathOrDefault(), arguments, envVars: environmentVariables);
if(hasListeningUri)
{
ListeningUri = GetListeningUri(output);
}
}


public void VisitInBrowser(IWebDriver driver)
{
_output.WriteLine($"Opening browser at {_listeningUri}...");
driver.Navigate().GoToUrl(_listeningUri);
_output.WriteLine($"Opening browser at {ListeningUri}...");
driver.Navigate().GoToUrl(ListeningUri);

if (driver is EdgeDriver)
{
Expand All @@ -75,7 +81,7 @@ public void VisitInBrowser(IWebDriver driver)
{
_output.WriteLine($"Clicking on link '{continueLink.Text}' to skip invalid certificate error page.");
continueLink.Click();
driver.Navigate().GoToUrl(_listeningUri);
driver.Navigate().GoToUrl(ListeningUri);
}
else
{
Expand All @@ -85,6 +91,58 @@ public void VisitInBrowser(IWebDriver driver)
}
}

public async Task AssertPagesOk(IEnumerable<Page> pages)
{
foreach (var page in pages)
{
await AssertOk(page.Url);
await ContainsLinks(page);
}
}

public async Task ContainsLinks(Page page)
{
var request = new HttpRequestMessage(
HttpMethod.Get,
new Uri(ListeningUri, page.Url));

var response = await _httpClient.SendAsync(request);

Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var parser = new HtmlParser();
var html = await parser.ParseAsync(await response.Content.ReadAsStreamAsync());

foreach (IHtmlLinkElement styleSheet in html.GetElementsByTagName("link"))
{
Assert.Equal("stylesheet", styleSheet.Relation);
await AssertOk(styleSheet.Href.Replace("about://", string.Empty));
}
foreach (var script in html.Scripts)
{
if (!string.IsNullOrEmpty(script.Source))
{
await AssertOk(script.Source);
}
}

Assert.True(html.Links.Length == page.Links.Count(), $"Expected {page.Url} to have {page.Links.Count()} links but it had {html.Links.Length}");
foreach ((var link, var expectedLink) in html.Links.Zip(page.Links, Tuple.Create))
{
IHtmlAnchorElement anchor = (IHtmlAnchorElement)link;
if (string.Equals(anchor.Protocol, "about:"))
{
Assert.True(anchor.PathName.EndsWith(expectedLink), $"Expected next link on {page.Url} to be {expectedLink} but it was {anchor.PathName}.");
await AssertOk(anchor.PathName);
}
else
{
Assert.True(string.Equals(anchor.Href, expectedLink), $"Expected next link to be {expectedLink} but it was {anchor.Href}.");
var result = await _httpClient.GetAsync(anchor.Href);
Assert.True(IsSuccessStatusCode(result), $"{anchor.Href} is a broken link!");
}
}
}

private Uri GetListeningUri(ITestOutputHelper output)
{
// Wait until the app is accepting HTTP requests
Expand Down Expand Up @@ -113,6 +171,11 @@ private Uri GetListeningUri(ITestOutputHelper output)
}
}

private bool IsSuccessStatusCode(HttpResponseMessage response)
{
return response.IsSuccessStatusCode || response.StatusCode == HttpStatusCode.Redirect;
}

public Task AssertOk(string requestUrl)
=> AssertStatusCode(requestUrl, HttpStatusCode.OK);

Expand All @@ -121,22 +184,22 @@ public Task AssertNotFound(string requestUrl)

internal Task<HttpResponseMessage> SendRequest(string path)
{
return _httpClient.GetAsync(new Uri(_listeningUri, path));
return _httpClient.GetAsync(new Uri(ListeningUri, path));
}

public async Task AssertStatusCode(string requestUrl, HttpStatusCode statusCode, string acceptContentType = null)
{
var request = new HttpRequestMessage(
HttpMethod.Get,
new Uri(_listeningUri, requestUrl));
new Uri(ListeningUri, requestUrl));

if (!string.IsNullOrEmpty(acceptContentType))
{
request.Headers.Add("Accept", acceptContentType);
}

var response = await _httpClient.SendAsync(request);
Assert.Equal(statusCode, response.StatusCode);
Assert.True(statusCode == response.StatusCode, $"Expected {requestUrl} to have status '{statusCode}' but it was '{response.StatusCode}'.");
}

public void Dispose()
Expand All @@ -153,7 +216,7 @@ public override string ToString()
{
if (!Process.HasExited)
{
result += $"(Listening on {_listeningUri.OriginalString}) PID: {Process.Id}";
result += $"(Listening on {ListeningUri.OriginalString}) PID: {Process.Id}";
}
else
{
Expand All @@ -164,4 +227,10 @@ public override string ToString()
return result;
}
}

public class Page
{
public string Url { get; set; }
public IEnumerable<string> Links { get; set; }
}
}
17 changes: 17 additions & 0 deletions src/ProjectTemplates/test/Helpers/PageUrls.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// 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.

namespace Templates.Test.Helpers
{
public static class PageUrls
{
public const string HomeUrl = "/";
public const string PrivacyUrl = "/Privacy";
public const string PrivacyFullUrl = "/Home/Privacy";
public const string DocsUrl = "https://docs.microsoft.com/aspnet/core";
public const string LoginUrl = "/Identity/Account/Login";
public const string RegisterUrl = "/Identity/Account/Register";
public const string ForgotPassword = "/Identity/Account/ForgotPassword";
public const string ExternalArticle = "https://go.microsoft.com/fwlink/?LinkID=532715";
}
}
Loading