Skip to content

Commit fefffd7

Browse files
Test all links in templates (#8628)
Improve templating tests
1 parent 237b19b commit fefffd7

13 files changed

+638
-65
lines changed

src/Middleware/NodeServices/src/Microsoft.AspNetCore.NodeServices.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
<Project Sdk="Microsoft.NET.Sdk">
2-
32
<PropertyGroup>
43
<Description>Invoke Node.js modules at runtime in ASP.NET Core applications.</Description>
54
<TargetFramework>netcoreapp3.0</TargetFramework>

src/ProjectTemplates/Web.ProjectTemplates/RazorClassLibrary-CSharp.csproj.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
<RazorLangVersion Condition="'$(SupportPagesAndViews)' != 'True'">3.0</RazorLangVersion>
77
<AddRazorSupportForMvc Condition="'$(SupportPagesAndViews)' == 'True'">true</AddRazorSupportForMvc>
88
<RootNamespace Condition="'$(name)' != '$(name{-VALUE-FORMS-}safe_namespace)'">Company.RazorClassLibrary1</RootNamespace>
9+
<AddRazorSupportForMvc Condition="'$(SupportPagesAndViews)' == 'True'">true</AddRazorSupportForMvc>
910
</PropertyGroup>
1011

1112
<ItemGroup Condition="'$(SupportPagesAndViews)' == 'True'">

src/ProjectTemplates/test/EmptyWebTemplateTest.cs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,14 @@ public EmptyWebTemplateTest(ProjectFactoryFixture projectFactory, ITestOutputHel
2222

2323
public ITestOutputHelper Output { get; }
2424

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

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

3335
var publishResult = await Project.RunDotNetPublishAsync();
@@ -43,8 +45,8 @@ public async Task EmptyWebTemplateAsync()
4345
using (var aspNetProcess = Project.StartBuiltProjectAsync())
4446
{
4547
Assert.False(
46-
aspNetProcess.Process.HasExited,
47-
ErrorMessages.GetFailedProcessMessageOrEmpty("Run built project", Project, aspNetProcess.Process));
48+
aspNetProcess.Process.HasExited,
49+
ErrorMessages.GetFailedProcessMessageOrEmpty("Run built project", Project, aspNetProcess.Process));
4850

4951
await aspNetProcess.AssertOk("/");
5052
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Threading.Tasks;
5+
using Templates.Test.Helpers;
6+
using Xunit;
7+
using Xunit.Abstractions;
8+
9+
namespace Templates.Test
10+
{
11+
public class GrpcTemplateTest
12+
{
13+
public GrpcTemplateTest(ProjectFactoryFixture projectFactory, ITestOutputHelper output)
14+
{
15+
ProjectFactory = projectFactory;
16+
Output = output;
17+
}
18+
19+
public Project Project { get; set; }
20+
21+
public ProjectFactoryFixture ProjectFactory { get; }
22+
public ITestOutputHelper Output { get; }
23+
24+
[Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/7973")]
25+
public async Task GrpcTemplate()
26+
{
27+
Project = await ProjectFactory.GetOrCreateProject("grpc", Output);
28+
29+
var createResult = await Project.RunDotNetNewAsync("grpc");
30+
Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", Project, createResult));
31+
32+
var publishResult = await Project.RunDotNetPublishAsync();
33+
Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", Project, publishResult));
34+
35+
var buildResult = await Project.RunDotNetBuildAsync();
36+
Assert.True(0 == buildResult.ExitCode, ErrorMessages.GetFailedProcessMessage("build", Project, buildResult));
37+
38+
using (var serverProcess = Project.StartBuiltServerAsync())
39+
{
40+
Assert.False(
41+
serverProcess.Process.HasExited,
42+
ErrorMessages.GetFailedProcessMessageOrEmpty("Run built server", Project, serverProcess.Process));
43+
44+
using (var clientProcess = Project.StartBuiltClientAsync(serverProcess))
45+
{
46+
// Wait for the client to do its thing
47+
await Task.Delay(100);
48+
Assert.False(
49+
clientProcess.Process.HasExited,
50+
ErrorMessages.GetFailedProcessMessageOrEmpty("Run built client", Project, clientProcess.Process));
51+
}
52+
}
53+
54+
using (var aspNetProcess = Project.StartPublishedServerAsync())
55+
{
56+
Assert.False(
57+
aspNetProcess.Process.HasExited,
58+
ErrorMessages.GetFailedProcessMessageOrEmpty("Run published server", Project, aspNetProcess.Process));
59+
60+
using (var clientProcess = Project.StartPublishedClientAsync())
61+
{
62+
// Wait for the client to do its thing
63+
await Task.Delay(100);
64+
Assert.False(
65+
clientProcess.Process.HasExited,
66+
ErrorMessages.GetFailedProcessMessageOrEmpty("Run built client", Project, clientProcess.Process));
67+
}
68+
}
69+
}
70+
}
71+
}

src/ProjectTemplates/test/Helpers/AspNetProcess.cs

Lines changed: 82 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
using System.Net;
99
using System.Net.Http;
1010
using System.Threading.Tasks;
11+
using AngleSharp.Dom.Html;
12+
using AngleSharp.Parser.Html;
1113
using Microsoft.AspNetCore.Certificates.Generation;
1214
using Microsoft.Extensions.CommandLineUtils;
1315
using OpenQA.Selenium;
@@ -21,17 +23,19 @@ namespace Templates.Test.Helpers
2123
public class AspNetProcess : IDisposable
2224
{
2325
private const string ListeningMessagePrefix = "Now listening on: ";
24-
private readonly Uri _listeningUri;
2526
private readonly HttpClient _httpClient;
2627
private readonly ITestOutputHelper _output;
2728

29+
internal readonly Uri ListeningUri;
2830
internal ProcessEx Process { get; }
2931

3032
public AspNetProcess(
3133
ITestOutputHelper output,
3234
string workingDirectory,
3335
string dllPath,
34-
IDictionary<string, string> environmentVariables)
36+
IDictionary<string, string> environmentVariables,
37+
bool published = true,
38+
bool hasListeningUri = true)
3539
{
3640
_output = output;
3741
_httpClient = new HttpClient(new HttpClientHandler()
@@ -48,18 +52,20 @@ public AspNetProcess(
4852
var now = DateTimeOffset.Now;
4953
new CertificateManager().EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1));
5054

51-
5255
output.WriteLine("Running ASP.NET application...");
5356

54-
Process = ProcessEx.Run(output, workingDirectory, DotNetMuxer.MuxerPathOrDefault(), $"exec {dllPath}", envVars: environmentVariables);
55-
_listeningUri = GetListeningUri(output);
57+
var arguments = published ? $"exec {dllPath}" : "run";
58+
Process = ProcessEx.Run(output, workingDirectory, DotNetMuxer.MuxerPathOrDefault(), arguments, envVars: environmentVariables);
59+
if(hasListeningUri)
60+
{
61+
ListeningUri = GetListeningUri(output);
62+
}
5663
}
5764

58-
5965
public void VisitInBrowser(IWebDriver driver)
6066
{
61-
_output.WriteLine($"Opening browser at {_listeningUri}...");
62-
driver.Navigate().GoToUrl(_listeningUri);
67+
_output.WriteLine($"Opening browser at {ListeningUri}...");
68+
driver.Navigate().GoToUrl(ListeningUri);
6369

6470
if (driver is EdgeDriver)
6571
{
@@ -75,7 +81,7 @@ public void VisitInBrowser(IWebDriver driver)
7581
{
7682
_output.WriteLine($"Clicking on link '{continueLink.Text}' to skip invalid certificate error page.");
7783
continueLink.Click();
78-
driver.Navigate().GoToUrl(_listeningUri);
84+
driver.Navigate().GoToUrl(ListeningUri);
7985
}
8086
else
8187
{
@@ -85,6 +91,58 @@ public void VisitInBrowser(IWebDriver driver)
8591
}
8692
}
8793

94+
public async Task AssertPagesOk(IEnumerable<Page> pages)
95+
{
96+
foreach (var page in pages)
97+
{
98+
await AssertOk(page.Url);
99+
await ContainsLinks(page);
100+
}
101+
}
102+
103+
public async Task ContainsLinks(Page page)
104+
{
105+
var request = new HttpRequestMessage(
106+
HttpMethod.Get,
107+
new Uri(ListeningUri, page.Url));
108+
109+
var response = await _httpClient.SendAsync(request);
110+
111+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
112+
var parser = new HtmlParser();
113+
var html = await parser.ParseAsync(await response.Content.ReadAsStreamAsync());
114+
115+
foreach (IHtmlLinkElement styleSheet in html.GetElementsByTagName("link"))
116+
{
117+
Assert.Equal("stylesheet", styleSheet.Relation);
118+
await AssertOk(styleSheet.Href.Replace("about://", string.Empty));
119+
}
120+
foreach (var script in html.Scripts)
121+
{
122+
if (!string.IsNullOrEmpty(script.Source))
123+
{
124+
await AssertOk(script.Source);
125+
}
126+
}
127+
128+
Assert.True(html.Links.Length == page.Links.Count(), $"Expected {page.Url} to have {page.Links.Count()} links but it had {html.Links.Length}");
129+
foreach ((var link, var expectedLink) in html.Links.Zip(page.Links, Tuple.Create))
130+
{
131+
IHtmlAnchorElement anchor = (IHtmlAnchorElement)link;
132+
if (string.Equals(anchor.Protocol, "about:"))
133+
{
134+
Assert.True(anchor.PathName.EndsWith(expectedLink), $"Expected next link on {page.Url} to be {expectedLink} but it was {anchor.PathName}.");
135+
await AssertOk(anchor.PathName);
136+
}
137+
else
138+
{
139+
Assert.True(string.Equals(anchor.Href, expectedLink), $"Expected next link to be {expectedLink} but it was {anchor.Href}.");
140+
var result = await _httpClient.GetAsync(anchor.Href);
141+
Assert.True(IsSuccessStatusCode(result), $"{anchor.Href} is a broken link!");
142+
}
143+
}
144+
}
145+
88146
private Uri GetListeningUri(ITestOutputHelper output)
89147
{
90148
// Wait until the app is accepting HTTP requests
@@ -113,6 +171,11 @@ private Uri GetListeningUri(ITestOutputHelper output)
113171
}
114172
}
115173

174+
private bool IsSuccessStatusCode(HttpResponseMessage response)
175+
{
176+
return response.IsSuccessStatusCode || response.StatusCode == HttpStatusCode.Redirect;
177+
}
178+
116179
public Task AssertOk(string requestUrl)
117180
=> AssertStatusCode(requestUrl, HttpStatusCode.OK);
118181

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

122185
internal Task<HttpResponseMessage> SendRequest(string path)
123186
{
124-
return _httpClient.GetAsync(new Uri(_listeningUri, path));
187+
return _httpClient.GetAsync(new Uri(ListeningUri, path));
125188
}
126189

127190
public async Task AssertStatusCode(string requestUrl, HttpStatusCode statusCode, string acceptContentType = null)
128191
{
129192
var request = new HttpRequestMessage(
130193
HttpMethod.Get,
131-
new Uri(_listeningUri, requestUrl));
194+
new Uri(ListeningUri, requestUrl));
132195

133196
if (!string.IsNullOrEmpty(acceptContentType))
134197
{
135198
request.Headers.Add("Accept", acceptContentType);
136199
}
137200

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

142205
public void Dispose()
@@ -153,7 +216,7 @@ public override string ToString()
153216
{
154217
if (!Process.HasExited)
155218
{
156-
result += $"(Listening on {_listeningUri.OriginalString}) PID: {Process.Id}";
219+
result += $"(Listening on {ListeningUri.OriginalString}) PID: {Process.Id}";
157220
}
158221
else
159222
{
@@ -164,4 +227,10 @@ public override string ToString()
164227
return result;
165228
}
166229
}
230+
231+
public class Page
232+
{
233+
public string Url { get; set; }
234+
public IEnumerable<string> Links { get; set; }
235+
}
167236
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
namespace Templates.Test.Helpers
5+
{
6+
public static class PageUrls
7+
{
8+
public const string HomeUrl = "/";
9+
public const string PrivacyUrl = "/Privacy";
10+
public const string PrivacyFullUrl = "/Home/Privacy";
11+
public const string DocsUrl = "https://docs.microsoft.com/aspnet/core";
12+
public const string LoginUrl = "/Identity/Account/Login";
13+
public const string RegisterUrl = "/Identity/Account/Register";
14+
public const string ForgotPassword = "/Identity/Account/ForgotPassword";
15+
public const string ExternalArticle = "https://go.microsoft.com/fwlink/?LinkID=532715";
16+
}
17+
}

0 commit comments

Comments
 (0)