diff --git a/src/Middleware/NodeServices/src/Microsoft.AspNetCore.NodeServices.csproj b/src/Middleware/NodeServices/src/Microsoft.AspNetCore.NodeServices.csproj index 27df62162b..3387ae5cdc 100644 --- a/src/Middleware/NodeServices/src/Microsoft.AspNetCore.NodeServices.csproj +++ b/src/Middleware/NodeServices/src/Microsoft.AspNetCore.NodeServices.csproj @@ -1,5 +1,4 @@  - Invoke Node.js modules at runtime in ASP.NET Core applications. netcoreapp3.0 diff --git a/src/ProjectTemplates/Web.ProjectTemplates/RazorClassLibrary-CSharp.csproj.in b/src/ProjectTemplates/Web.ProjectTemplates/RazorClassLibrary-CSharp.csproj.in index 4bd3778901..27bdffb00b 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/RazorClassLibrary-CSharp.csproj.in +++ b/src/ProjectTemplates/Web.ProjectTemplates/RazorClassLibrary-CSharp.csproj.in @@ -6,6 +6,7 @@ 3.0 true Company.RazorClassLibrary1 + true diff --git a/src/ProjectTemplates/test/EmptyWebTemplateTest.cs b/src/ProjectTemplates/test/EmptyWebTemplateTest.cs index 21f794f090..3f71d38b65 100644 --- a/src/ProjectTemplates/test/EmptyWebTemplateTest.cs +++ b/src/ProjectTemplates/test/EmptyWebTemplateTest.cs @@ -22,12 +22,14 @@ namespace Templates.Test 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(); @@ -43,8 +45,8 @@ namespace Templates.Test 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("/"); } diff --git a/src/ProjectTemplates/test/GrpcTemplateTest.cs b/src/ProjectTemplates/test/GrpcTemplateTest.cs new file mode 100644 index 0000000000..0d9a5ce803 --- /dev/null +++ b/src/ProjectTemplates/test/GrpcTemplateTest.cs @@ -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 + { + 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() + { + 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)); + } + } + } + } +} diff --git a/src/ProjectTemplates/test/Helpers/AspNetProcess.cs b/src/ProjectTemplates/test/Helpers/AspNetProcess.cs index e7b1ea50e8..90480320cf 100644 --- a/src/ProjectTemplates/test/Helpers/AspNetProcess.cs +++ b/src/ProjectTemplates/test/Helpers/AspNetProcess.cs @@ -8,6 +8,8 @@ using System.Linq; 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; @@ -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 environmentVariables) + IDictionary environmentVariables, + bool published = true, + bool hasListeningUri = true) { _output = output; _httpClient = new HttpClient(new HttpClientHandler() @@ -48,18 +52,20 @@ namespace Templates.Test.Helpers 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) { @@ -75,7 +81,7 @@ namespace Templates.Test.Helpers { _output.WriteLine($"Clicking on link '{continueLink.Text}' to skip invalid certificate error page."); continueLink.Click(); - driver.Navigate().GoToUrl(_listeningUri); + driver.Navigate().GoToUrl(ListeningUri); } else { @@ -85,6 +91,58 @@ namespace Templates.Test.Helpers } } + public async Task AssertPagesOk(IEnumerable 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 @@ -113,6 +171,11 @@ namespace Templates.Test.Helpers } } + private bool IsSuccessStatusCode(HttpResponseMessage response) + { + return response.IsSuccessStatusCode || response.StatusCode == HttpStatusCode.Redirect; + } + public Task AssertOk(string requestUrl) => AssertStatusCode(requestUrl, HttpStatusCode.OK); @@ -121,14 +184,14 @@ namespace Templates.Test.Helpers internal Task 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)) { @@ -136,7 +199,7 @@ namespace Templates.Test.Helpers } 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() @@ -153,7 +216,7 @@ namespace Templates.Test.Helpers { if (!Process.HasExited) { - result += $"(Listening on {_listeningUri.OriginalString}) PID: {Process.Id}"; + result += $"(Listening on {ListeningUri.OriginalString}) PID: {Process.Id}"; } else { @@ -164,4 +227,10 @@ namespace Templates.Test.Helpers return result; } } + + public class Page + { + public string Url { get; set; } + public IEnumerable Links { get; set; } + } } diff --git a/src/ProjectTemplates/test/Helpers/PageUrls.cs b/src/ProjectTemplates/test/Helpers/PageUrls.cs new file mode 100644 index 0000000000..afb0783cbc --- /dev/null +++ b/src/ProjectTemplates/test/Helpers/PageUrls.cs @@ -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"; + } +} diff --git a/src/ProjectTemplates/test/Helpers/Project.cs b/src/ProjectTemplates/test/Helpers/Project.cs index 4fa536b2cc..ace64ad31e 100644 --- a/src/ProjectTemplates/test/Helpers/Project.cs +++ b/src/ProjectTemplates/test/Helpers/Project.cs @@ -19,6 +19,8 @@ namespace Templates.Test.Helpers [DebuggerDisplay("{ToString(),nq}")] public class Project { + private const string _urls = "http://127.0.0.1:0;https://127.0.0.1:0"; + public const string DefaultFramework = "netcoreapp3.0"; public SemaphoreSlim DotNetNewLock { get; set; } @@ -30,6 +32,12 @@ namespace Templates.Test.Helpers public string TemplateBuildDir => Path.Combine(TemplateOutputDir, "bin", "Debug", DefaultFramework); public string TemplatePublishDir => Path.Combine(TemplateOutputDir, "bin", "Release", DefaultFramework, "publish"); + private string TemplateServerDir => Path.Combine(TemplateOutputDir, $"{ProjectName}.Server"); + private string TemplateClientDir => Path.Combine(TemplateOutputDir, $"{ProjectName}.Client"); + public string TemplateClientDebugDir => Path.Combine(TemplateClientDir, "bin", "Debug", DefaultFramework); + public string TemplateClientReleaseDir => Path.Combine(TemplateClientDir, "bin", "Release", DefaultFramework, "publish"); + public string TemplateServerReleaseDir => Path.Combine(TemplateServerDir, "bin", "Release", DefaultFramework, "publish"); + public ITestOutputHelper Output { get; set; } public IMessageSink DiagnosticsMessageSink { get; set; } @@ -127,11 +135,55 @@ namespace Templates.Test.Helpers } } + internal AspNetProcess StartBuiltServerAsync() + { + var environment = new Dictionary + { + ["ASPNETCORE_ENVIRONMENT"] = "Development" + }; + + var projectDll = Path.Combine(TemplateServerDir, $"{ProjectName}.Server.dll"); + return new AspNetProcess(Output, TemplateServerDir, projectDll, environment, published: false); + } + + internal AspNetProcess StartBuiltClientAsync(AspNetProcess serverProcess) + { + var environment = new Dictionary + { + ["ASPNETCORE_ENVIRONMENT"] = "Development" + }; + + var projectDll = Path.Combine(TemplateClientDebugDir, $"{ProjectName}.Client.dll {serverProcess.ListeningUri.Port}"); + return new AspNetProcess(Output, TemplateOutputDir, projectDll, environment, hasListeningUri: false); + } + + internal AspNetProcess StartPublishedServerAsync() + { + var environment = new Dictionary + { + ["ASPNETCORE_URLS"] = _urls, + }; + + var projectDll = $"{ProjectName}.Server.dll"; + return new AspNetProcess(Output, TemplateServerReleaseDir, projectDll, environment); + } + + internal AspNetProcess StartPublishedClientAsync() + { + var environment = new Dictionary + { + ["ASPNETCORE_URLS"] = _urls, + }; + + var projectDll = $"{ProjectName}.Client.dll"; + return new AspNetProcess(Output, TemplateClientReleaseDir, projectDll, environment); + } + internal AspNetProcess StartBuiltProjectAsync() { var environment = new Dictionary { - ["ASPNETCORE_URLS"] = $"http://127.0.0.1:0;https://127.0.0.1:0", + ["ASPNETCORE_URLS"] = _urls, ["ASPNETCORE_ENVIRONMENT"] = "Development" }; @@ -143,7 +195,7 @@ namespace Templates.Test.Helpers { var environment = new Dictionary { - ["ASPNETCORE_URLS"] = $"http://127.0.0.1:0;https://127.0.0.1:0", + ["ASPNETCORE_URLS"] = _urls, }; var projectDll = $"{ProjectName}.dll"; diff --git a/src/ProjectTemplates/test/MvcTemplateTest.cs b/src/ProjectTemplates/test/MvcTemplateTest.cs index a17fc356ba..59c38cf22b 100644 --- a/src/ProjectTemplates/test/MvcTemplateTest.cs +++ b/src/ProjectTemplates/test/MvcTemplateTest.cs @@ -1,9 +1,11 @@ // 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.Collections.Generic; using System.IO; using System.Threading.Tasks; using Templates.Test.Helpers; +using System.Linq; using Xunit; using Xunit.Abstractions; @@ -55,14 +57,39 @@ namespace Templates.Test var buildResult = await Project.RunDotNetBuildAsync(); Assert.True(0 == buildResult.ExitCode, ErrorMessages.GetFailedProcessMessage("build", Project, buildResult)); + IEnumerable menuLinks = new List { + PageUrls.HomeUrl, + PageUrls.HomeUrl, + PageUrls.PrivacyFullUrl + }; + + if(languageOverride == null) + { + menuLinks = menuLinks.Append(PageUrls.PrivacyFullUrl); + } + var footerLinks = new string[] { PageUrls.PrivacyFullUrl }; + + var pages = new List + { + new Page + { + Url = PageUrls.HomeUrl, + Links = menuLinks.Append(PageUrls.DocsUrl).Concat(footerLinks) + }, + new Page + { + Url = PageUrls.PrivacyFullUrl, + Links = menuLinks.Concat(footerLinks) + } + }; + using (var aspNetProcess = Project.StartBuiltProjectAsync()) { Assert.False( aspNetProcess.Process.HasExited, ErrorMessages.GetFailedProcessMessageOrEmpty("Run built project", Project, aspNetProcess.Process)); - await aspNetProcess.AssertOk("/"); - await aspNetProcess.AssertOk("/Home/Privacy"); + await aspNetProcess.AssertPagesOk(pages); } using (var aspNetProcess = Project.StartPublishedProjectAsync()) @@ -71,8 +98,7 @@ namespace Templates.Test aspNetProcess.Process.HasExited, ErrorMessages.GetFailedProcessMessageOrEmpty("Run published project", Project, aspNetProcess.Process)); - await aspNetProcess.AssertOk("/"); - await aspNetProcess.AssertOk("/Home/Privacy"); + await aspNetProcess.AssertPagesOk(pages); } } @@ -110,18 +136,94 @@ namespace Templates.Test Assert.True(0 == migrationsResult.ExitCode, ErrorMessages.GetFailedProcessMessage("run EF migrations", Project, migrationsResult)); Project.AssertEmptyMigration("mvc"); + var pages = new List { + new Page + { + Url = PageUrls.ForgotPassword, + Links = new string [] { + PageUrls.HomeUrl, + PageUrls.RegisterUrl, + PageUrls.LoginUrl, + PageUrls.HomeUrl, + PageUrls.PrivacyUrl, + PageUrls.PrivacyUrl, + PageUrls.PrivacyUrl + } + }, + new Page + { + Url = PageUrls.HomeUrl, + Links = new string[] { + PageUrls.HomeUrl, + PageUrls.RegisterUrl, + PageUrls.LoginUrl, + PageUrls.HomeUrl, + PageUrls.PrivacyUrl, + PageUrls.PrivacyUrl, + PageUrls.DocsUrl, + PageUrls.PrivacyUrl + } + }, + new Page + { + Url = PageUrls.PrivacyFullUrl, + Links = new string[] { + PageUrls.HomeUrl, + PageUrls.RegisterUrl, + PageUrls.LoginUrl, + PageUrls.HomeUrl, + PageUrls.PrivacyUrl, + PageUrls.PrivacyUrl, + PageUrls.PrivacyUrl + } + }, + new Page + { + Url = PageUrls.LoginUrl, + Links = new string[] { + PageUrls.HomeUrl, + PageUrls.RegisterUrl, + PageUrls.LoginUrl, + PageUrls.HomeUrl, + PageUrls.PrivacyUrl, + PageUrls.PrivacyUrl, + PageUrls.ForgotPassword, + PageUrls.RegisterUrl, + PageUrls.ExternalArticle, + PageUrls.PrivacyUrl } + }, + new Page + { + Url = PageUrls.RegisterUrl, + Links = new string [] { + PageUrls.HomeUrl, + PageUrls.RegisterUrl, + PageUrls.LoginUrl, + PageUrls.HomeUrl, + PageUrls.PrivacyUrl, + PageUrls.PrivacyUrl, + PageUrls.ExternalArticle, + PageUrls.PrivacyUrl + } + } + }; + using (var aspNetProcess = Project.StartBuiltProjectAsync()) { - await aspNetProcess.AssertOk("/"); - await aspNetProcess.AssertOk("/Identity/Account/Login"); - await aspNetProcess.AssertOk("/Home/Privacy"); + Assert.False( + aspNetProcess.Process.HasExited, + ErrorMessages.GetFailedProcessMessageOrEmpty("Run built project", Project, aspNetProcess.Process)); + + await aspNetProcess.AssertPagesOk(pages); } using (var aspNetProcess = Project.StartPublishedProjectAsync()) { - await aspNetProcess.AssertOk("/"); - await aspNetProcess.AssertOk("/Identity/Account/Login"); - await aspNetProcess.AssertOk("/Home/Privacy"); + Assert.False( + aspNetProcess.Process.HasExited, + ErrorMessages.GetFailedProcessMessageOrEmpty("Run published project", Project, aspNetProcess.Process)); + + await aspNetProcess.AssertPagesOk(pages); } } diff --git a/src/ProjectTemplates/test/RazorClassLibraryTemplateTest.cs b/src/ProjectTemplates/test/RazorClassLibraryTemplateTest.cs new file mode 100644 index 0000000000..bfafbede5e --- /dev/null +++ b/src/ProjectTemplates/test/RazorClassLibraryTemplateTest.cs @@ -0,0 +1,43 @@ +// 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 RazorClassLibraryTemplateTest + { + public RazorClassLibraryTemplateTest(ProjectFactoryFixture projectFactory, ITestOutputHelper output) + { + ProjectFactory = projectFactory; + Output = output; + } + + public Project Project { get; set; } + + public ProjectFactoryFixture ProjectFactory { get; } + public ITestOutputHelper Output { get; } + + [Fact] + public async Task RazorClassLibraryTemplateAsync() + { + Project = await ProjectFactory.GetOrCreateProject("razorclasslib", Output); + + var createResult = await Project.RunDotNetNewAsync("razorclasslib"); + 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)); + + // Run dotnet build after publish. The reason is that one uses Config = Debug and the other uses Config = Release + // The output from publish will go into bin/Release/netcoreapp3.0/publish and won't be affected by calling build + // later, while the opposite is not true. + + var buildResult = await Project.RunDotNetBuildAsync(); + Assert.True(0 == buildResult.ExitCode, ErrorMessages.GetFailedProcessMessage("build", Project, buildResult)); + } + } +} diff --git a/src/ProjectTemplates/test/RazorPagesTemplateTest.cs b/src/ProjectTemplates/test/RazorPagesTemplateTest.cs index 4a1ad311f9..d7a432147e 100644 --- a/src/ProjectTemplates/test/RazorPagesTemplateTest.cs +++ b/src/ProjectTemplates/test/RazorPagesTemplateTest.cs @@ -1,6 +1,7 @@ // 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.Collections.Generic; using System.IO; using System.Threading.Tasks; using Templates.Test.Helpers; @@ -31,8 +32,6 @@ namespace Templates.Test var createResult = await Project.RunDotNetNewAsync("razor"); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("razor", Project, createResult)); - AssertFileExists(Project.TemplateOutputDir, "Pages/Shared/_LoginPartial.cshtml", false); - var projectFileContents = ReadFile(Project.TemplateOutputDir, $"{Project.ProjectName}.csproj"); Assert.DoesNotContain(".db", projectFileContents); Assert.DoesNotContain("Microsoft.EntityFrameworkCore.Tools", projectFileContents); @@ -50,14 +49,39 @@ namespace Templates.Test var buildResult = await Project.RunDotNetBuildAsync(); Assert.True(0 == buildResult.ExitCode, ErrorMessages.GetFailedProcessMessage("build", Project, createResult)); + var pages = new List + { + new Page + { + Url = PageUrls.HomeUrl, + Links = new string[] { + PageUrls.HomeUrl, + PageUrls.HomeUrl, + PageUrls.PrivacyUrl, + PageUrls.PrivacyUrl, + PageUrls.DocsUrl, + PageUrls.PrivacyUrl + } + }, + new Page + { + Url = PageUrls.PrivacyUrl, + Links = new string[] { + PageUrls.HomeUrl, + PageUrls.HomeUrl, + PageUrls.PrivacyUrl, + PageUrls.PrivacyUrl, + PageUrls.PrivacyUrl } + } + }; + using (var aspNetProcess = Project.StartBuiltProjectAsync()) { Assert.False( aspNetProcess.Process.HasExited, ErrorMessages.GetFailedProcessMessageOrEmpty("Run built project", Project, aspNetProcess.Process)); - await aspNetProcess.AssertOk("/"); - await aspNetProcess.AssertOk("/Privacy"); + await aspNetProcess.AssertPagesOk(pages); } using (var aspNetProcess = Project.StartPublishedProjectAsync()) @@ -66,8 +90,7 @@ namespace Templates.Test aspNetProcess.Process.HasExited, ErrorMessages.GetFailedProcessMessageOrEmpty("Run published project", Project, aspNetProcess.Process)); - await aspNetProcess.AssertOk("/"); - await aspNetProcess.AssertOk("/Privacy"); + await aspNetProcess.AssertPagesOk(pages); } } @@ -81,8 +104,6 @@ namespace Templates.Test var createResult = await Project.RunDotNetNewAsync("razor", auth: "Individual", useLocalDB: useLocalDB); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", Project, createResult)); - AssertFileExists(Project.TemplateOutputDir, "Pages/Shared/_LoginPartial.cshtml", true); - var projectFileContents = ReadFile(Project.TemplateOutputDir, $"{Project.ProjectName}.csproj"); if (!useLocalDB) { @@ -103,39 +124,104 @@ namespace Templates.Test Assert.True(0 == migrationsResult.ExitCode, ErrorMessages.GetFailedProcessMessage("run EF migrations", Project, migrationsResult)); Project.AssertEmptyMigration("razorpages"); + var pages = new List { + new Page + { + Url = PageUrls.ForgotPassword, + Links = new string [] { + PageUrls.HomeUrl, + PageUrls.RegisterUrl, + PageUrls.LoginUrl, + PageUrls.HomeUrl, + PageUrls.PrivacyUrl, + PageUrls.PrivacyUrl, + PageUrls.PrivacyUrl + } + }, + new Page + { + Url = PageUrls.HomeUrl, + Links = new string[] { + PageUrls.HomeUrl, + PageUrls.RegisterUrl, + PageUrls.LoginUrl, + PageUrls.HomeUrl, + PageUrls.PrivacyUrl, + PageUrls.PrivacyUrl, + PageUrls.DocsUrl, + PageUrls.PrivacyUrl + } + }, + new Page + { + Url = PageUrls.PrivacyUrl, + Links = new string[] { + PageUrls.HomeUrl, + PageUrls.RegisterUrl, + PageUrls.LoginUrl, + PageUrls.HomeUrl, + PageUrls.PrivacyUrl, + PageUrls.PrivacyUrl, + PageUrls.PrivacyUrl + } + }, + new Page + { + Url = PageUrls.LoginUrl, + Links = new string[] { + PageUrls.HomeUrl, + PageUrls.RegisterUrl, + PageUrls.LoginUrl, + PageUrls.HomeUrl, + PageUrls.PrivacyUrl, + PageUrls.PrivacyUrl, + PageUrls.ForgotPassword, + PageUrls.RegisterUrl, + PageUrls.ExternalArticle, + PageUrls.PrivacyUrl } + }, + new Page + { + Url = PageUrls.RegisterUrl, + Links = new string [] { + PageUrls.HomeUrl, + PageUrls.RegisterUrl, + PageUrls.LoginUrl, + PageUrls.HomeUrl, + PageUrls.PrivacyUrl, + PageUrls.PrivacyUrl, + PageUrls.ExternalArticle, + PageUrls.PrivacyUrl + } + } + }; + using (var aspNetProcess = Project.StartBuiltProjectAsync()) { - await aspNetProcess.AssertOk("/"); - await aspNetProcess.AssertOk("/Identity/Account/Login"); - await aspNetProcess.AssertOk("/Privacy"); + Assert.False( + aspNetProcess.Process.HasExited, + ErrorMessages.GetFailedProcessMessageOrEmpty("Run built project", Project, aspNetProcess.Process)); + + await aspNetProcess.AssertPagesOk(pages); } using (var aspNetProcess = Project.StartPublishedProjectAsync()) { - await aspNetProcess.AssertOk("/"); - await aspNetProcess.AssertOk("/Identity/Account/Login"); - await aspNetProcess.AssertOk("/Privacy"); + Assert.False( + aspNetProcess.Process.HasExited, + ErrorMessages.GetFailedProcessMessageOrEmpty("Run built project", Project, aspNetProcess.Process)); + + await aspNetProcess.AssertPagesOk(pages); } } - private void AssertFileExists(string basePath, string path, bool shouldExist) + + private string ReadFile(string basePath, string path) { var fullPath = Path.Combine(basePath, path); var doesExist = File.Exists(fullPath); - if (shouldExist) - { - Assert.True(doesExist, "Expected file to exist, but it doesn't: " + path); - } - else - { - Assert.False(doesExist, "Expected file not to exist, but it does: " + path); - } - } - - private string ReadFile(string basePath, string path) - { - AssertFileExists(basePath, path, shouldExist: true); + Assert.True(doesExist, $"Expected file to exist, but it doesn't: {path}"); return File.ReadAllText(Path.Combine(basePath, path)); } } diff --git a/src/ProjectTemplates/test/WebApiTemplateTest.cs b/src/ProjectTemplates/test/WebApiTemplateTest.cs index b70529c837..3069e84d2c 100644 --- a/src/ProjectTemplates/test/WebApiTemplateTest.cs +++ b/src/ProjectTemplates/test/WebApiTemplateTest.cs @@ -22,12 +22,14 @@ namespace Templates.Test public Project Project { get; set; } - [Fact] - public async Task WebApiTemplateAsync() + [Theory] + [InlineData(null)] + [InlineData("F#")] + public async Task WebApiTemplateAsync(string languageOverride) { - Project = await FactoryFixture.GetOrCreateProject("webapi", Output); + Project = await FactoryFixture.GetOrCreateProject("webapi" + (languageOverride == "F#" ? "fsharp" : "csharp"), Output); - var createResult = await Project.RunDotNetNewAsync("webapi"); + var createResult = await Project.RunDotNetNewAsync("webapi", language: languageOverride); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", Project, createResult)); var publishResult = await Project.RunDotNetPublishAsync(); diff --git a/src/ProjectTemplates/test/WorkerTemplateTest.cs b/src/ProjectTemplates/test/WorkerTemplateTest.cs new file mode 100644 index 0000000000..b66672c7d8 --- /dev/null +++ b/src/ProjectTemplates/test/WorkerTemplateTest.cs @@ -0,0 +1,56 @@ +// 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 WorkerTemplateTest + { + public WorkerTemplateTest(ProjectFactoryFixture projectFactory, ITestOutputHelper output) + { + ProjectFactory = projectFactory; + Output = output; + } + + public Project Project { get; set; } + public ProjectFactoryFixture ProjectFactory { get; } + public ITestOutputHelper Output { get; } + + [Fact(Skip = "Microsoft.NET.Sdk.Worker isn't available yet")] + public async Task WorkerTemplateAsync() + { + Project = await ProjectFactory.GetOrCreateProject("worker", Output); + + var createResult = await Project.RunDotNetNewAsync("worker"); + 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)); + + // Run dotnet build after publish. The reason is that one uses Config = Debug and the other uses Config = Release + // The output from publish will go into bin/Release/netcoreapp3.0/publish and won't be affected by calling build + // later, while the opposite is not true. + + var buildResult = await Project.RunDotNetBuildAsync(); + Assert.True(0 == buildResult.ExitCode, ErrorMessages.GetFailedProcessMessage("build", Project, buildResult)); + + using (var aspNetProcess = Project.StartBuiltProjectAsync()) + { + Assert.False( + aspNetProcess.Process.HasExited, + ErrorMessages.GetFailedProcessMessageOrEmpty("Run built project", Project, aspNetProcess.Process)); + } + + using (var aspNetProcess = Project.StartPublishedProjectAsync()) + { + Assert.False( + aspNetProcess.Process.HasExited, + ErrorMessages.GetFailedProcessMessageOrEmpty("Run published project", Project, aspNetProcess.Process)); + } + } + } +} diff --git a/src/ProjectTemplates/test/template-baselines.json b/src/ProjectTemplates/test/template-baselines.json index 348e4a375d..cbe25a4521 100644 --- a/src/ProjectTemplates/test/template-baselines.json +++ b/src/ProjectTemplates/test/template-baselines.json @@ -370,7 +370,7 @@ } }, "web": { - "None": { + "CSharp": { "Template": "web", "Arguments": "new web", "Files": [ @@ -381,6 +381,18 @@ "Properties/launchSettings.json" ], "AuthOption": "None" + }, + "FSharp": { + "Template": "web", + "Arguments": "new web --language F#", + "Files": [ + "appsettings.Development.json", + "appsettings.json", + "Program.fs", + "Startup.fs", + "Properties/launchSettings.json" + ], + "AuthOption": "None" } }, "webapi": { @@ -435,6 +447,18 @@ "Properties/launchSettings.json" ], "AuthOption": "Windows" + }, + "FSharp": { + "Template": "webapi", + "Arguments": "new webapi --language F#", + "Files": [ + "appsettings.Development.json", + "appsettings.json", + "Program.fs", + "Startup.fs", + "Controllers/ValuesController.fs", + "Properties/launchSettings.json" + ] } }, "mvc": { @@ -864,6 +888,55 @@ ] } }, + "razorcomponents": { + "None": { + "Template": "razorcomponents", + "Arguments": "new razorcomponents", + "Files": [ + "Pages/_Imports.razor", + "Pages/Counter.razor", + "Pages/FetchData.razor", + "Pages/_Host.cshtml", + "Pages/Index.razor", + "Shared/MainLayout.razor", + "Shared/NavMenu.razor", + "_Imports.razor", + "App.razor", + "Properties/launchSettings.json", + "Data/WeatherForecast.cs", + "Data/WeatherForecastService.cs", + "wwwroot/css/bootstrap/bootstrap.min.css", + "wwwroot/css/bootstrap/bootstrap.min.css.map", + "wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css", + "wwwroot/css/open-iconic/font/fonts/open-iconic.eot", + "wwwroot/css/open-iconic/font/fonts/open-iconic.otf", + "wwwroot/css/open-iconic/font/fonts/open-iconic.svg", + "wwwroot/css/open-iconic/font/fonts/open-iconic.ttf", + "wwwroot/css/open-iconic/font/fonts/open-iconic.woff", + "wwwroot/css/open-iconic/FONT-LICENSE", + "wwwroot/css/open-iconic/ICON-LICENSE", + "wwwroot/css/open-iconic/README.md", + "wwwroot/css/site.css", + "appsettings.Development.json", + "appsettings.json", + "Program.cs", + "Startup.cs" + ] + } + }, + "worker": { + "None": { + "Template": "worker", + "Arguments": "new worker", + "Files": [ + "Properties/launchSettings.json", + "appsettings.Development.json", + "appsettings.json", + "Program.cs", + "Worker.cs" + ] + } + }, "angular": { "None": { "Template": "angular",