From 3418843621a9b74bccc3b41cf1fa62eae471233e Mon Sep 17 00:00:00 2001 From: Damian Edwards Date: Mon, 16 May 2022 16:17:09 -0700 Subject: [PATCH] Fix templates sourceName & port of template fixes from main (#41536) * Fix templates sourceName * Support minimal APIs & program/main together in Web API template - Updated test cases to cover more options combinations - Fixed route pattern typo in minimal APIs case - Added missing UseAuthorization call in minimal APIs + Windows Auth case - Fail template tests if a compiler warning is output on build or publish - Fix Web API template when call graph option is true - Add arg constants & fix formatting Fixes #41491 * Clean-up from template fix port - Update template scripts to use correct package version when using locally - Update template-baselines.json to cover new template options * Update for VS compiler changes to nullability & u8 string literals * Template baselines test fix * Make template baseline test check namespaces match project name * Comment for clarity * Port extra template tests from main * Port template test change that fails if warnings present on build or publish * Use original file name in template baseline test * Fix single file exe test Now that we fail if "warning" appears in the command output, we have to be sure to now issue and dotnet CLI commands that result in SDK warnings (in this case the change in net6.0 that -r must be accompanied with --self-contained or --no-self-contained). * Comment out tenmplate baseline test namespace declaration * Comment out template warning checks * Apply test fixes from failures investigation * Set project name * Bump Helix test runner timeout to 60 mins * Collect test host dumps on Helix test runner crashes * Print message when test job times out Bump helix timeout to match main (45 mins) * Make helix runner print timestamps with console logs * Ported more test changes from main * Revert failing project test if new/build/publish emit restore errors or other warnings. * Fix typos * Port TestRunner.cs changes --- eng/helix/content/RunTests/ProcessUtil.cs | 8 +- eng/helix/content/RunTests/Program.cs | 10 +- eng/helix/content/RunTests/TestRunner.cs | 114 +++++---- eng/targets/Helix.props | 3 +- src/Hosting/Hosting/src/Internal/WebHost.cs | 6 +- .../BlazorTemplateTest.cs | 2 +- src/ProjectTemplates/Shared/ProcessResult.cs | 2 +- src/ProjectTemplates/Shared/Project.cs | 117 +++------ .../Shared/ProjectFactoryFixture.cs | 53 +++-- .../BlazorServerWeb-CSharp/Program.Main.cs | 2 +- .../Client/Program.Main.cs | 6 +- .../Server/Program.Main.cs | 2 +- .../GrpcService-CSharp/Program.Main.cs | 2 +- .../.template.config/template.json | 18 +- .../content/WebApi-CSharp/Program.Main.cs | 223 +++++++++++++----- ...gram.MinimalAPIs.OrgOrIndividualB2CAuth.cs | 14 +- .../Program.MinimalAPIs.WindowsOrNoAuth.cs | 1 + .../content/Worker-CSharp/Program.Main.cs | 4 +- .../scripts/Run-Angular-Locally.ps1 | 2 +- .../Run-AngularProgramMain-Locally.ps1 | 2 +- .../scripts/Run-Blazor-Locally.ps1 | 2 +- .../scripts/Run-BlazorProgramMain-Locally.ps1 | 2 +- .../scripts/Run-BlazorWasm-Locally.ps1 | 2 +- .../Run-BlazorWasmProgramMain-Locally.ps1 | 2 +- .../scripts/Run-EmptyWeb-Locally.ps1 | 2 +- .../Run-EmptyWebProgramMain-Locally.ps1 | 2 +- .../scripts/Run-Razor-Locally.ps1 | 2 +- .../scripts/Run-RazorProgramMain-Locally.ps1 | 2 +- .../scripts/Run-React-Locally.ps1 | 2 +- .../scripts/Run-ReactProgramMain-Locally.ps1 | 2 +- .../scripts/Run-ReactRedux-Locally.ps1 | 2 +- .../scripts/Run-Starterweb-Locally.ps1 | 2 +- .../Run-StarterwebProgramMain-Locally.ps1 | 2 +- .../scripts/Run-WebApi-Locally.ps1 | 2 +- .../scripts/Run-WebApiMinimal-Locally.ps1 | 2 +- .../scripts/Run-WebApiProgamMain-Locally.ps1 | 2 +- .../Run-WebApiProgamMainMinimal-Locally.ps1 | 12 + .../scripts/Run-Worker-Locally.ps1 | 2 +- .../scripts/Run-WorkerProgramMain-Locally.ps1 | 2 +- .../scripts/Run-gRPC-Locally.ps1 | 2 +- src/ProjectTemplates/test/ArgConstants.cs | 27 +++ .../test/AssemblyInfo.AssemblyFixtures.cs | 2 + src/ProjectTemplates/test/BaselineTest.cs | 69 +----- .../test/BlazorServerTemplateTest.cs | 41 ++-- .../test/BlazorTemplateTest.cs | 4 +- .../test/BlazorWasmTemplateTest.cs | 211 ++++++++++++----- .../test/EmptyWebTemplateTest.cs | 4 +- src/ProjectTemplates/test/GrpcTemplateTest.cs | 4 +- .../test/IdentityUIPackageTest.cs | 2 +- .../ItemTemplateTests/BlazorServerTests.cs | 2 +- src/ProjectTemplates/test/MvcTemplateTest.cs | 48 ++-- .../test/ProjectTemplates.Tests.csproj | 3 + .../test/RazorClassLibraryTemplateTest.cs | 4 +- .../test/RazorPagesTemplateTest.cs | 51 ++-- src/ProjectTemplates/test/SpaTemplatesTest.cs | 12 +- .../test/WebApiTemplateTest.cs | 72 ++++-- .../test/WorkerTemplateTest.cs | 6 +- .../test/template-baselines.json | 100 ++++++++ .../CertificateManager.cs | 4 +- src/Testing/src/xunit/HelixConstants.cs | 1 + 60 files changed, 830 insertions(+), 478 deletions(-) create mode 100644 src/ProjectTemplates/scripts/Run-WebApiProgamMainMinimal-Locally.ps1 create mode 100644 src/ProjectTemplates/test/ArgConstants.cs diff --git a/eng/helix/content/RunTests/ProcessUtil.cs b/eng/helix/content/RunTests/ProcessUtil.cs index 416ec05a9087..4437e9b40b34 100644 --- a/eng/helix/content/RunTests/ProcessUtil.cs +++ b/eng/helix/content/RunTests/ProcessUtil.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Globalization; using System.IO; using System.Runtime.InteropServices; using System.Text; @@ -80,7 +81,7 @@ public static async Task RunAsync( Action? onStart = null, CancellationToken cancellationToken = default) { - Console.WriteLine($"Running '{filename} {arguments}'"); + PrintMessage($"Running '{filename} {arguments}'"); using var process = new Process() { StartInfo = @@ -153,7 +154,7 @@ public static async Task RunAsync( process.Exited += (_, e) => { - Console.WriteLine($"'{process.StartInfo.FileName} {process.StartInfo.Arguments}' completed with exit code '{process.ExitCode}'"); + PrintMessage($"'{process.StartInfo.FileName} {process.StartInfo.Arguments}' completed with exit code '{process.ExitCode}'"); if (throwOnError && process.ExitCode != 0) { processLifetimeTask.TrySetException(new InvalidOperationException($"Command {filename} {arguments} returned exit code {process.ExitCode} output: {outputBuilder.ToString()}")); @@ -208,5 +209,8 @@ public static async Task RunAsync( return await processLifetimeTask.Task; } + + public static void PrintMessage(string message) => Console.WriteLine($"{DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture)} {message}"); + public static void PrintErrorMessage(string message) => Console.Error.WriteLine($"{DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture)} {message}"); } } diff --git a/eng/helix/content/RunTests/Program.cs b/eng/helix/content/RunTests/Program.cs index 7247c80c364d..e3cbbfbe3a44 100644 --- a/eng/helix/content/RunTests/Program.cs +++ b/eng/helix/content/RunTests/Program.cs @@ -25,7 +25,7 @@ static async Task Main(string[] args) keepGoing = await runner.InstallPlaywrightAsync(); } #else - Console.WriteLine("Playwright install skipped."); + ProcessUtil.PrintMessage("Playwright install skipped."); #endif runner.DisplayContents(); @@ -34,23 +34,23 @@ static async Task Main(string[] args) { if (!await runner.CheckTestDiscoveryAsync()) { - Console.WriteLine("RunTest stopping due to test discovery failure."); + ProcessUtil.PrintMessage("RunTest stopping due to test discovery failure."); Environment.Exit(1); return; } var exitCode = await runner.RunTestsAsync(); runner.UploadResults(); - Console.WriteLine($"Completed Helix job with exit code '{exitCode}'"); + ProcessUtil.PrintMessage($"Completed Helix job with exit code '{exitCode}'"); Environment.Exit(exitCode); } - Console.WriteLine("Tests were not run due to previous failures. Exit code=1"); + ProcessUtil.PrintMessage("Tests were not run due to previous failures. Exit code=1"); Environment.Exit(1); } catch (Exception e) { - Console.WriteLine($"RunTests uncaught exception: {e.ToString()}"); + ProcessUtil.PrintMessage($"RunTests uncaught exception: {e.ToString()}"); Environment.Exit(1); } } diff --git a/eng/helix/content/RunTests/TestRunner.cs b/eng/helix/content/RunTests/TestRunner.cs index f08fac5fc260..d951ea512f93 100644 --- a/eng/helix/content/RunTests/TestRunner.cs +++ b/eng/helix/content/RunTests/TestRunner.cs @@ -33,36 +33,36 @@ public bool SetupEnvironment() EnvironmentVariables.Add("PATH", Options.Path); EnvironmentVariables.Add("helix", Options.HelixQueue); - Console.WriteLine($"Current Directory: {Options.HELIX_WORKITEM_ROOT}"); + ProcessUtil.PrintMessage($"Current Directory: {Options.HELIX_WORKITEM_ROOT}"); var helixDir = Options.HELIX_WORKITEM_ROOT; - Console.WriteLine($"Setting HELIX_DIR: {helixDir}"); + ProcessUtil.PrintMessage($"Setting HELIX_DIR: {helixDir}"); EnvironmentVariables.Add("HELIX_DIR", helixDir); EnvironmentVariables.Add("NUGET_FALLBACK_PACKAGES", helixDir); var nugetRestore = Path.Combine(helixDir, "nugetRestore"); EnvironmentVariables.Add("NUGET_RESTORE", nugetRestore); var dotnetEFFullPath = Path.Combine(nugetRestore, helixDir, "dotnet-ef.exe"); - Console.WriteLine($"Set DotNetEfFullPath: {dotnetEFFullPath}"); + ProcessUtil.PrintMessage($"Set DotNetEfFullPath: {dotnetEFFullPath}"); EnvironmentVariables.Add("DotNetEfFullPath", dotnetEFFullPath); var appRuntimePath = $"{Options.DotnetRoot}/shared/Microsoft.AspNetCore.App/{Options.RuntimeVersion}"; - Console.WriteLine($"Set ASPNET_RUNTIME_PATH: {appRuntimePath}"); + ProcessUtil.PrintMessage($"Set ASPNET_RUNTIME_PATH: {appRuntimePath}"); EnvironmentVariables.Add("ASPNET_RUNTIME_PATH", appRuntimePath); var dumpPath = Environment.GetEnvironmentVariable("HELIX_DUMP_FOLDER"); - Console.WriteLine($"Set VSTEST_DUMP_PATH: {dumpPath}"); + ProcessUtil.PrintMessage($"Set VSTEST_DUMP_PATH: {dumpPath}"); EnvironmentVariables.Add("VSTEST_DUMP_PATH", dumpPath); #if INSTALLPLAYWRIGHT // Playwright will download and look for browsers to this directory var playwrightBrowsers = Environment.GetEnvironmentVariable("PLAYWRIGHT_BROWSERS_PATH"); - Console.WriteLine($"Setting PLAYWRIGHT_BROWSERS_PATH: {playwrightBrowsers}"); + ProcessUtil.PrintMessage($"Setting PLAYWRIGHT_BROWSERS_PATH: {playwrightBrowsers}"); EnvironmentVariables.Add("PLAYWRIGHT_BROWSERS_PATH", playwrightBrowsers); var playrightDriver = Environment.GetEnvironmentVariable("PLAYWRIGHT_DRIVER_PATH"); - Console.WriteLine($"Setting PLAYWRIGHT_DRIVER_PATH: {playrightDriver}"); + ProcessUtil.PrintMessage($"Setting PLAYWRIGHT_DRIVER_PATH: {playrightDriver}"); EnvironmentVariables.Add("PLAYWRIGHT_DRIVER_PATH", playrightDriver); #else - Console.WriteLine($"Skipping setting PLAYWRIGHT_BROWSERS_PATH"); + ProcessUtil.PrintMessage($"Skipping setting PLAYWRIGHT_BROWSERS_PATH"); #endif - Console.WriteLine($"Creating nuget restore directory: {nugetRestore}"); + ProcessUtil.PrintMessage($"Creating nuget restore directory: {nugetRestore}"); Directory.CreateDirectory(nugetRestore); // Rename default.runner.json to xunit.runner.json if there is not a custom one from the project @@ -80,7 +80,7 @@ public bool SetupEnvironment() } catch (Exception e) { - Console.WriteLine($"Exception in SetupEnvironment: {e.ToString()}"); + ProcessUtil.PrintMessage($"Exception in SetupEnvironment: {e.ToString()}"); return false; } } @@ -90,20 +90,20 @@ public void DisplayContents(string path = "./") try { Console.WriteLine(); - Console.WriteLine($"Displaying directory contents for {path}:"); + ProcessUtil.PrintMessage($"Displaying directory contents for {path}:"); foreach (var file in Directory.EnumerateFiles(path)) { - Console.WriteLine(Path.GetFileName(file)); + ProcessUtil.PrintMessage(Path.GetFileName(file)); } foreach (var file in Directory.EnumerateDirectories(path)) { - Console.WriteLine(Path.GetFileName(file)); + ProcessUtil.PrintMessage(Path.GetFileName(file)); } Console.WriteLine(); } catch (Exception e) { - Console.WriteLine($"Exception in DisplayContents: {e.ToString()}"); + ProcessUtil.PrintMessage($"Exception in DisplayContents: {e.ToString()}"); } } @@ -112,14 +112,14 @@ public async Task InstallPlaywrightAsync() { try { - Console.WriteLine($"Installing Playwright to Browsers: {Environment.GetEnvironmentVariable("PLAYWRIGHT_BROWSERS_PATH")} Driver: {Environment.GetEnvironmentVariable("PLAYWRIGHT_DRIVER_PATH")}"); + ProcessUtil.PrintMessage($"Installing Playwright to Browsers: {Environment.GetEnvironmentVariable("PLAYWRIGHT_BROWSERS_PATH")} Driver: {Environment.GetEnvironmentVariable("PLAYWRIGHT_DRIVER_PATH")}"); await Playwright.InstallAsync(Environment.GetEnvironmentVariable("PLAYWRIGHT_BROWSERS_PATH"), Environment.GetEnvironmentVariable("PLAYWRIGHT_DRIVER_PATH")); DisplayContents(Environment.GetEnvironmentVariable("PLAYWRIGHT_BROWSERS_PATH")); return true; } catch (Exception e) { - Console.WriteLine($"Exception installing playwright: {e.ToString()}"); + ProcessUtil.PrintMessage($"Exception installing playwright: {e.ToString()}"); return false; } } @@ -133,18 +133,18 @@ public async Task InstallDotnetToolsAsync() await ProcessUtil.RunAsync($"{Options.DotnetRoot}/dotnet", $"tool install dotnet-dump --tool-path {Options.HELIX_WORKITEM_ROOT} --version 5.0.0-*", environmentVariables: EnvironmentVariables, - outputDataReceived: Console.WriteLine, - errorDataReceived: Console.Error.WriteLine, + outputDataReceived: ProcessUtil.PrintMessage, + errorDataReceived: ProcessUtil.PrintErrorMessage, throwOnError: false, cancellationToken: new CancellationTokenSource(TimeSpan.FromMinutes(2)).Token); - Console.WriteLine($"Adding current directory to nuget sources: {Options.HELIX_WORKITEM_ROOT}"); + ProcessUtil.PrintMessage($"Adding current directory to nuget sources: {Options.HELIX_WORKITEM_ROOT}"); await ProcessUtil.RunAsync($"{Options.DotnetRoot}/dotnet", $"nuget add source {Options.HELIX_WORKITEM_ROOT} --configfile NuGet.config", environmentVariables: EnvironmentVariables, - outputDataReceived: Console.WriteLine, - errorDataReceived: Console.Error.WriteLine, + outputDataReceived: ProcessUtil.PrintMessage, + errorDataReceived: ProcessUtil.PrintErrorMessage, throwOnError: false, cancellationToken: new CancellationTokenSource(TimeSpan.FromMinutes(2)).Token); @@ -152,24 +152,24 @@ await ProcessUtil.RunAsync($"{Options.DotnetRoot}/dotnet", await ProcessUtil.RunAsync($"{Options.DotnetRoot}/dotnet", "nuget list source", environmentVariables: EnvironmentVariables, - outputDataReceived: Console.WriteLine, - errorDataReceived: Console.Error.WriteLine, + outputDataReceived: ProcessUtil.PrintMessage, + errorDataReceived: ProcessUtil.PrintErrorMessage, throwOnError: false, cancellationToken: new CancellationTokenSource(TimeSpan.FromMinutes(2)).Token); await ProcessUtil.RunAsync($"{Options.DotnetRoot}/dotnet", $"tool install dotnet-ef --version {Options.EfVersion} --tool-path {Options.HELIX_WORKITEM_ROOT}", environmentVariables: EnvironmentVariables, - outputDataReceived: Console.WriteLine, - errorDataReceived: Console.Error.WriteLine, + outputDataReceived: ProcessUtil.PrintMessage, + errorDataReceived: ProcessUtil.PrintErrorMessage, throwOnError: false, cancellationToken: new CancellationTokenSource(TimeSpan.FromMinutes(2)).Token); await ProcessUtil.RunAsync($"{Options.DotnetRoot}/dotnet", $"tool install dotnet-serve --tool-path {Options.HELIX_WORKITEM_ROOT}", environmentVariables: EnvironmentVariables, - outputDataReceived: Console.WriteLine, - errorDataReceived: Console.Error.WriteLine, + outputDataReceived: ProcessUtil.PrintMessage, + errorDataReceived: ProcessUtil.PrintErrorMessage, throwOnError: false, cancellationToken: new CancellationTokenSource(TimeSpan.FromMinutes(2)).Token); @@ -177,7 +177,7 @@ await ProcessUtil.RunAsync($"{Options.DotnetRoot}/dotnet", } catch (Exception e) { - Console.WriteLine($"Exception in InstallDotnetTools: {e}"); + ProcessUtil.PrintMessage($"Exception in InstallDotnetTools: {e}"); return false; } } @@ -194,15 +194,15 @@ public async Task CheckTestDiscoveryAsync() if (discoveryResult.StandardOutput.Contains("Exception thrown")) { - Console.WriteLine("Exception thrown during test discovery."); - Console.WriteLine(discoveryResult.StandardOutput); + ProcessUtil.PrintMessage("Exception thrown during test discovery."); + ProcessUtil.PrintMessage(discoveryResult.StandardOutput); return false; } return true; } catch (Exception e) { - Console.WriteLine($"Exception in CheckTestDiscovery: {e.ToString()}"); + ProcessUtil.PrintMessage($"Exception in CheckTestDiscovery: {e.ToString()}"); return false; } } @@ -213,50 +213,60 @@ public async Task RunTestsAsync() try { // Timeout test run 5 minutes before the Helix job would timeout - var cts = new CancellationTokenSource(Options.Timeout.Subtract(TimeSpan.FromMinutes(5))); + var testProcessTimeout = Options.Timeout.Subtract(TimeSpan.FromMinutes(5)); + var cts = new CancellationTokenSource(testProcessTimeout); var diagLog = Path.Combine(Environment.GetEnvironmentVariable("HELIX_WORKITEM_UPLOAD_ROOT"), "vstest.log"); - var commonTestArgs = $"test {Options.Target} --diag:{diagLog} --logger:xunit --logger:\"console;verbosity=normal\" --blame \"CollectHangDump;TestTimeout=15m\""; + var commonTestArgs = $"test {Options.Target} --diag:{diagLog} --logger xunit --logger \"console;verbosity=normal\" " + + "--blame-crash --blame-hang-timeout 15m"; if (Options.Quarantined) { - Console.WriteLine("Running quarantined tests."); + ProcessUtil.PrintMessage("Running quarantined tests."); // Filter syntax: https://github.com/Microsoft/vstest-docs/blob/master/docs/filter.md var result = await ProcessUtil.RunAsync($"{Options.DotnetRoot}/dotnet", commonTestArgs + " --TestCaseFilter:\"Quarantined=true\"", environmentVariables: EnvironmentVariables, - outputDataReceived: Console.WriteLine, - errorDataReceived: Console.Error.WriteLine, + outputDataReceived: ProcessUtil.PrintMessage, + errorDataReceived: ProcessUtil.PrintErrorMessage, throwOnError: false, cancellationToken: cts.Token); + if (cts.Token.IsCancellationRequested) + { + ProcessUtil.PrintMessage($"Quarantined tests exceeded configured timeout: {testProcessTimeout.TotalMinutes}m."); + } if (result.ExitCode != 0) { - Console.WriteLine($"Failure in quarantined tests. Exit code: {result.ExitCode}."); + ProcessUtil.PrintMessage($"Failure in quarantined tests. Exit code: {result.ExitCode}."); } } else { - Console.WriteLine("Running non-quarantined tests."); + ProcessUtil.PrintMessage("Running non-quarantined tests."); // Filter syntax: https://github.com/Microsoft/vstest-docs/blob/master/docs/filter.md var result = await ProcessUtil.RunAsync($"{Options.DotnetRoot}/dotnet", commonTestArgs + " --TestCaseFilter:\"Quarantined!=true|Quarantined=false\"", environmentVariables: EnvironmentVariables, - outputDataReceived: Console.WriteLine, - errorDataReceived: Console.Error.WriteLine, + outputDataReceived: ProcessUtil.PrintMessage, + errorDataReceived: ProcessUtil.PrintErrorMessage, throwOnError: false, cancellationToken: cts.Token); + if (cts.Token.IsCancellationRequested) + { + ProcessUtil.PrintMessage($"Non-quarantined tests exceeded configured timeout: {testProcessTimeout.TotalMinutes}m."); + } if (result.ExitCode != 0) { - Console.WriteLine($"Failure in non-quarantined tests. Exit code: {result.ExitCode}."); + ProcessUtil.PrintMessage($"Failure in non-quarantined tests. Exit code: {result.ExitCode}."); exitCode = result.ExitCode; } } } catch (Exception e) { - Console.WriteLine($"Exception in RunTests: {e.ToString()}"); + ProcessUtil.PrintMessage($"Exception in RunTests: {e.ToString()}"); exitCode = 1; } return exitCode; @@ -265,51 +275,51 @@ public async Task RunTestsAsync() public void UploadResults() { // 'testResults.xml' is the file Helix looks for when processing test results - Console.WriteLine("Trying to upload results..."); + ProcessUtil.PrintMessage("Trying to upload results..."); if (File.Exists("TestResults/TestResults.xml")) { - Console.WriteLine("Copying TestResults/TestResults.xml to ./testResults.xml"); + ProcessUtil.PrintMessage("Copying TestResults/TestResults.xml to ./testResults.xml"); File.Copy("TestResults/TestResults.xml", "testResults.xml"); } else { - Console.WriteLine("No test results found."); + ProcessUtil.PrintMessage("No test results found."); } var HELIX_WORKITEM_UPLOAD_ROOT = Environment.GetEnvironmentVariable("HELIX_WORKITEM_UPLOAD_ROOT"); if (string.IsNullOrEmpty(HELIX_WORKITEM_UPLOAD_ROOT)) { - Console.WriteLine("No HELIX_WORKITEM_UPLOAD_ROOT specified, skipping log copy"); + ProcessUtil.PrintMessage("No HELIX_WORKITEM_UPLOAD_ROOT specified, skipping log copy"); return; } - Console.WriteLine($"Copying artifacts/log/ to {HELIX_WORKITEM_UPLOAD_ROOT}/"); + ProcessUtil.PrintMessage($"Copying artifacts/log/ to {HELIX_WORKITEM_UPLOAD_ROOT}/"); if (Directory.Exists("artifacts/log")) { foreach (var file in Directory.EnumerateFiles("artifacts/log", "*.log", SearchOption.AllDirectories)) { // Combine the directory name + log name for the copied log file name to avoid overwriting duplicate test names in different test projects var logName = $"{Path.GetFileName(Path.GetDirectoryName(file))}_{Path.GetFileName(file)}"; - Console.WriteLine($"Copying: {file} to {Path.Combine(HELIX_WORKITEM_UPLOAD_ROOT, logName)}"); + ProcessUtil.PrintMessage($"Copying: {file} to {Path.Combine(HELIX_WORKITEM_UPLOAD_ROOT, logName)}"); File.Copy(file, Path.Combine(HELIX_WORKITEM_UPLOAD_ROOT, logName)); } } else { - Console.WriteLine("No logs found in artifacts/log"); + ProcessUtil.PrintMessage("No logs found in artifacts/log"); } - Console.WriteLine($"Copying TestResults/**/Sequence*.xml to {HELIX_WORKITEM_UPLOAD_ROOT}/"); + ProcessUtil.PrintMessage($"Copying TestResults/**/Sequence*.xml to {HELIX_WORKITEM_UPLOAD_ROOT}/"); if (Directory.Exists("TestResults")) { foreach (var file in Directory.EnumerateFiles("TestResults", "Sequence*.xml", SearchOption.AllDirectories)) { var fileName = Path.GetFileName(file); - Console.WriteLine($"Copying: {file} to {Path.Combine(HELIX_WORKITEM_UPLOAD_ROOT, fileName)}"); + ProcessUtil.PrintMessage($"Copying: {file} to {Path.Combine(HELIX_WORKITEM_UPLOAD_ROOT, fileName)}"); File.Copy(file, Path.Combine(HELIX_WORKITEM_UPLOAD_ROOT, fileName)); } } else { - Console.WriteLine("No TestResults directory found."); + ProcessUtil.PrintMessage("No TestResults directory found."); } } } diff --git a/eng/targets/Helix.props b/eng/targets/Helix.props index dc2bad9552b9..3554c48dd037 100644 --- a/eng/targets/Helix.props +++ b/eng/targets/Helix.props @@ -11,8 +11,7 @@ true - 00:30:00 - 00:40:00 + 00:45:00 false $(MSBuildProjectName)--$(TargetFramework) false diff --git a/src/Hosting/Hosting/src/Internal/WebHost.cs b/src/Hosting/Hosting/src/Internal/WebHost.cs index ca52bd1f400b..bf9e339ddb51 100644 --- a/src/Hosting/Hosting/src/Internal/WebHost.cs +++ b/src/Hosting/Hosting/src/Internal/WebHost.cs @@ -81,12 +81,12 @@ public WebHost( // There's no way to to register multiple service types per definition. See https://github.com/aspnet/DependencyInjection/issues/360 #pragma warning disable CS8634 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'class' constraint. _applicationServiceCollection.AddSingleton(services - => services.GetService() as IHostApplicationLifetime); + => services.GetService()! as IHostApplicationLifetime); #pragma warning disable CS0618 // Type or member is obsolete _applicationServiceCollection.AddSingleton(services - => services.GetService() as AspNetCore.Hosting.IApplicationLifetime); + => services.GetService()! as AspNetCore.Hosting.IApplicationLifetime); _applicationServiceCollection.AddSingleton(services - => services.GetService() as Extensions.Hosting.IApplicationLifetime); + => services.GetService()! as Extensions.Hosting.IApplicationLifetime); #pragma warning restore CS0618 // Type or member is obsolete #pragma warning restore CS8634 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'class' constraint. _applicationServiceCollection.AddSingleton(); diff --git a/src/ProjectTemplates/BlazorTemplates.Tests/BlazorTemplateTest.cs b/src/ProjectTemplates/BlazorTemplates.Tests/BlazorTemplateTest.cs index 7ff582dcd896..3a8523a5e23f 100644 --- a/src/ProjectTemplates/BlazorTemplates.Tests/BlazorTemplateTest.cs +++ b/src/ProjectTemplates/BlazorTemplates.Tests/BlazorTemplateTest.cs @@ -29,7 +29,7 @@ protected async Task CreateBuildPublishAsync(string projectName, string // Additional arguments are needed. See: https://github.com/dotnet/aspnetcore/issues/24278 Environment.SetEnvironmentVariable("EnableDefaultScopedCssItems", "true"); - var project = await ProjectFactory.GetOrCreateProject(projectName, Output); + var project = await ProjectFactory.CreateProject(Output); if (targetFramework != null) { project.TargetFramework = targetFramework; diff --git a/src/ProjectTemplates/Shared/ProcessResult.cs b/src/ProjectTemplates/Shared/ProcessResult.cs index 81ff0f2bd13b..474f252f2e80 100644 --- a/src/ProjectTemplates/Shared/ProcessResult.cs +++ b/src/ProjectTemplates/Shared/ProcessResult.cs @@ -17,7 +17,7 @@ public ProcessResult(ProcessEx process) public string Process { get; } - public int ExitCode { get; } + public int ExitCode { get; set; } public string Error { get; } diff --git a/src/ProjectTemplates/Shared/Project.cs b/src/ProjectTemplates/Shared/Project.cs index f78a7d4ab238..14e4c1985e1a 100644 --- a/src/ProjectTemplates/Shared/Project.cs +++ b/src/ProjectTemplates/Shared/Project.cs @@ -54,6 +54,7 @@ internal async Task RunDotNetNewAsync( string language = null, bool useLocalDB = false, bool noHttps = false, + bool errorOnRestoreError = true, string[] args = null, // Used to set special options in MSBuild IDictionary environmentVariables = null) @@ -95,29 +96,15 @@ internal async Task RunDotNetNewAsync( argString += $" -o {TemplateOutputDir}"; - // Only run one instance of 'dotnet new' at once, as a workaround for - // https://github.com/aspnet/templating/issues/63 - - await DotNetNewLock.WaitAsync(); - try - { - Output.WriteLine("Acquired DotNetNewLock"); - - if (Directory.Exists(TemplateOutputDir)) - { - Output.WriteLine($"Template directory already exists, deleting contents of {TemplateOutputDir}"); - Directory.Delete(TemplateOutputDir, recursive: true); - } - - using var execution = ProcessEx.Run(Output, AppContext.BaseDirectory, DotNetMuxer.MuxerPathOrDefault(), argString, environmentVariables); - await execution.Exited; - return new ProcessResult(execution); - } - finally + if (Directory.Exists(TemplateOutputDir)) { - DotNetNewLock.Release(); - Output.WriteLine("Released DotNetNewLock"); + Output.WriteLine($"Template directory already exists, deleting contents of {TemplateOutputDir}"); + Directory.Delete(TemplateOutputDir, recursive: true); } + + using var execution = ProcessEx.Run(Output, AppContext.BaseDirectory, DotNetMuxer.MuxerPathOrDefault(), argString, environmentVariables); + await execution.Exited; + return new ProcessResult(execution); } internal async Task RunDotNetPublishAsync(IDictionary packageOptions = null, string additionalArgs = null, bool noRestore = true) @@ -183,31 +170,19 @@ internal async Task RunDotNetEfCreateMigrationAsync(string migrat { var args = $"--verbose --no-build migrations add {migrationName}"; - // Only run one instance of 'dotnet new' at once, as a workaround for - // https://github.com/aspnet/templating/issues/63 - await DotNetNewLock.WaitAsync(); - try + var command = DotNetMuxer.MuxerPathOrDefault(); + if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DotNetEfFullPath"))) { - Output.WriteLine("Acquired DotNetNewLock"); - var command = DotNetMuxer.MuxerPathOrDefault(); - if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DotNetEfFullPath"))) - { - args = $"\"{DotNetEfFullPath}\" " + args; - } - else - { - command = "dotnet-ef"; - } - - using var result = ProcessEx.Run(Output, TemplateOutputDir, command, args); - await result.Exited; - return new ProcessResult(result); + args = $"\"{DotNetEfFullPath}\" " + args; } - finally + else { - DotNetNewLock.Release(); - Output.WriteLine("Released DotNetNewLock"); + command = "dotnet-ef"; } + + using var result = ProcessEx.Run(Output, TemplateOutputDir, command, args); + await result.Exited; + return new ProcessResult(result); } internal async Task RunDotNetEfUpdateDatabaseAsync() @@ -216,31 +191,19 @@ internal async Task RunDotNetEfUpdateDatabaseAsync() var args = "--verbose --no-build database update"; - // Only run one instance of 'dotnet new' at once, as a workaround for - // https://github.com/aspnet/templating/issues/63 - await DotNetNewLock.WaitAsync(); - try + var command = DotNetMuxer.MuxerPathOrDefault(); + if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DotNetEfFullPath"))) { - Output.WriteLine("Acquired DotNetNewLock"); - var command = DotNetMuxer.MuxerPathOrDefault(); - if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DotNetEfFullPath"))) - { - args = $"\"{DotNetEfFullPath}\" " + args; - } - else - { - command = "dotnet-ef"; - } - - using var result = ProcessEx.Run(Output, TemplateOutputDir, command, args); - await result.Exited; - return new ProcessResult(result); + args = $"\"{DotNetEfFullPath}\" " + args; } - finally + else { - DotNetNewLock.Release(); - Output.WriteLine("Released DotNetNewLock"); + command = "dotnet-ef"; } + + using var result = ProcessEx.Run(Output, TemplateOutputDir, command, args); + await result.Exited; + return new ProcessResult(result); } // If this fails, you should generate new migrations via migrations/updateMigrations.cmd @@ -294,25 +257,15 @@ public string ReadFile(string path) internal async Task RunDotNetNewRawAsync(string arguments) { - await DotNetNewLock.WaitAsync(); - try - { - Output.WriteLine("Acquired DotNetNewLock"); - var result = ProcessEx.Run( - Output, - AppContext.BaseDirectory, - DotNetMuxer.MuxerPathOrDefault(), - arguments + - $" --debug:disable-sdk-templates --debug:custom-hive \"{TemplatePackageInstaller.CustomHivePath}\"" + - $" -o {TemplateOutputDir}"); - await result.Exited; - return result; - } - finally - { - DotNetNewLock.Release(); - Output.WriteLine("Released DotNetNewLock"); - } + var result = ProcessEx.Run( + Output, + AppContext.BaseDirectory, + DotNetMuxer.MuxerPathOrDefault(), + arguments + + $" --debug:disable-sdk-templates --debug:custom-hive \"{TemplatePackageInstaller.CustomHivePath}\"" + + $" -o {TemplateOutputDir}"); + await result.Exited; + return result; } public void Dispose() diff --git a/src/ProjectTemplates/Shared/ProjectFactoryFixture.cs b/src/ProjectTemplates/Shared/ProjectFactoryFixture.cs index 9af84efc680d..f18518286018 100644 --- a/src/ProjectTemplates/Shared/ProjectFactoryFixture.cs +++ b/src/ProjectTemplates/Shared/ProjectFactoryFixture.cs @@ -14,6 +14,7 @@ namespace Templates.Test.Helpers { public class ProjectFactoryFixture : IDisposable { + private const string LetterChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; private readonly ConcurrentDictionary _projects = new ConcurrentDictionary(); public IMessageSink DiagnosticsMessageSink { get; } @@ -23,6 +24,21 @@ public ProjectFactoryFixture(IMessageSink diagnosticsMessageSink) DiagnosticsMessageSink = diagnosticsMessageSink; } + public async Task CreateProject(ITestOutputHelper output) + { + await TemplatePackageInstaller.EnsureTemplatingEngineInitializedAsync(output); + + var project = CreateProjectImpl(output); + + var projectKey = Guid.NewGuid().ToString().Substring(0, 10).ToLowerInvariant(); + if (!_projects.TryAdd(projectKey, project)) + { + throw new InvalidOperationException($"Project key collision in {nameof(ProjectFactoryFixture)}.{nameof(CreateProject)}!"); + } + + return project; + } + public async Task GetOrCreateProject(string projectKey, ITestOutputHelper output) { await TemplatePackageInstaller.EnsureTemplatingEngineInitializedAsync(output); @@ -34,24 +50,31 @@ public async Task GetOrCreateProject(string projectKey, ITestOutputHelp } return _projects.GetOrAdd( projectKey, - (key, outputHelper) => - { - var project = new Project - { - Output = outputHelper, - DiagnosticsMessageSink = DiagnosticsMessageSink, - ProjectGuid = Path.GetRandomFileName().Replace(".", string.Empty) - }; - project.ProjectName = $"AspNet.{project.ProjectGuid}"; - - var assemblyPath = GetType().Assembly; - var basePath = GetTemplateFolderBasePath(assemblyPath); - project.TemplateOutputDir = Path.Combine(basePath, project.ProjectName); - return project; - }, + (_, outputHelper) => CreateProjectImpl(outputHelper), output); } + private Project CreateProjectImpl(ITestOutputHelper output) + { + var project = new Project + { + Output = output, + DiagnosticsMessageSink = DiagnosticsMessageSink, + // Ensure first character is a letter to avoid random insertions of '_' into template namespace + // declarations (i.e. make it more stable for testing) + ProjectGuid = GetRandomLetter() + Path.GetRandomFileName().Replace(".", string.Empty) + }; + project.ProjectName = $"AspNetCore.{project.ProjectGuid}"; + + var assemblyPath = GetType().Assembly; + var basePath = GetTemplateFolderBasePath(assemblyPath); + project.TemplateOutputDir = Path.Combine(basePath, project.ProjectName); + + return project; + } + + private static char GetRandomLetter() => LetterChars[Random.Shared.Next(LetterChars.Length - 1)]; + private static string GetTemplateFolderBasePath(Assembly assembly) => (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("HELIX_DIR"))) ? assembly.GetCustomAttributes() diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Program.Main.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Program.Main.cs index 92eb45d80a91..a0b654422797 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Program.Main.cs +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Program.Main.cs @@ -37,7 +37,7 @@ #endif using BlazorServerWeb_CSharp.Data; -namespace Company.WebApplication1; +namespace BlazorServerWeb_CSharp; public class Program { diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Program.Main.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Program.Main.cs index 8b870e5dc9d3..18be2444cb69 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Program.Main.cs +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Program.Main.cs @@ -9,7 +9,11 @@ using ComponentsWebAssembly_CSharp; #endif -namespace Company.WebApplication1; +#if (Hosted) +namespace ComponentsWebAssembly_CSharp.Client; +#else +namespace ComponentsWebAssembly_CSharp; +#endif public class Program { diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Program.Main.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Program.Main.cs index 31835439cd28..f6e53531c467 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Program.Main.cs +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Program.Main.cs @@ -19,7 +19,7 @@ using ComponentsWebAssembly_CSharp.Server.Models; #endif -namespace Company.WebApplication1; +namespace ComponentsWebAssembly_CSharp; public class Program { diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/Program.Main.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/Program.Main.cs index ec1af1a7e9c6..04fc97d104f1 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/Program.Main.cs +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/Program.Main.cs @@ -1,6 +1,6 @@ using GrpcService_CSharp.Services; -namespace Company.WebApplication1; +namespace GrpcService_CSharp; public class Program { diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/.template.config/template.json index 5c9b53aedcd1..9cf6ab2fa69e 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/.template.config/template.json @@ -44,9 +44,11 @@ ] }, { - "condition": "(UseProgramMain && !UseMinimalAPIs)", + "condition": "(UseProgramMain)", "exclude": [ - "Program.cs" + "Program.cs", + "Program.MinimalAPIs.OrgOrIndividualB2CAuth.cs", + "Program.MinimalAPIs.WindowsOrNoAuth.cs" ], "rename": { "Program.Main.cs": "Program.cs" @@ -55,13 +57,17 @@ { "condition": "(UseMinimalAPIs)", "exclude": [ - "Controllers/WeatherForecastController.cs", - "Program.cs", + "Controllers/WeatherForecastController.cs" + ] + }, + { + "condition": "(UseMinimalAPIs && !UseProgramMain)", + "exclude": [ "WeatherForecast.cs" ] }, { - "condition": "(UseMinimalAPIs && (NoAuth || WindowsAuth))", + "condition": "(!UseProgramMain && UseMinimalAPIs && (NoAuth || WindowsAuth))", "rename": { "Program.MinimalAPIs.WindowsOrNoAuth.cs": "Program.cs" }, @@ -70,7 +76,7 @@ ] }, { - "condition": "(UseMinimalAPIs && (IndividualAuth || OrganizationalAuth))", + "condition": "(!UseProgramMain && UseMinimalAPIs && (IndividualAuth || OrganizationalAuth))", "rename": { "Program.MinimalAPIs.OrgOrIndividualB2CAuth.cs": "Program.cs" }, diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Program.Main.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Program.Main.cs index a0c9ad67e817..a882ad35c648 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Program.Main.cs +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Program.Main.cs @@ -10,6 +10,7 @@ #endif #if (OrganizationalAuth || IndividualB2CAuth) using Microsoft.Identity.Web; +using Microsoft.Identity.Web.Resource; #endif #if (OrganizationalAuth || IndividualB2CAuth || GenerateGraph || WindowsAuth) @@ -20,76 +21,168 @@ public class Program { public static void Main(string[] args) { - var builder = WebApplication.CreateBuilder(args); - - // Add services to the container. - #if (OrganizationalAuth) - builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) - #if (GenerateApiOrGraph) - .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd")) - .EnableTokenAcquisitionToCallDownstreamApi() - #if (GenerateApi) - .AddDownstreamWebApi("DownstreamApi", builder.Configuration.GetSection("DownstreamApi")) - #endif - #if (GenerateGraph) - .AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApi")) - #endif - .AddInMemoryTokenCaches(); - #else - .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd")); - #endif - #elif (IndividualB2CAuth) - builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) - #if (GenerateApi) - .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAdB2C")) - .EnableTokenAcquisitionToCallDownstreamApi() - .AddDownstreamWebApi("DownstreamApi", builder.Configuration.GetSection("DownstreamApi")) - .AddInMemoryTokenCaches(); - #else - .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAdB2C")); - #endif - #endif - - builder.Services.AddControllers(); - #if (EnableOpenAPI) - // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle - builder.Services.AddEndpointsApiExplorer(); - builder.Services.AddSwaggerGen(); - #endif - #if (WindowsAuth) - - builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme) - .AddNegotiate(); - - builder.Services.AddAuthorization(options => - { - // By default, all incoming requests will be authorized according to the default policy. - options.FallbackPolicy = options.DefaultPolicy; - }); - #endif + var builder = WebApplication.CreateBuilder(args); - var app = builder.Build(); + // Add services to the container. + #if (OrganizationalAuth) + builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + #if (GenerateApiOrGraph) + .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd")) + .EnableTokenAcquisitionToCallDownstreamApi() + #if (GenerateApi) + .AddDownstreamWebApi("DownstreamApi", builder.Configuration.GetSection("DownstreamApi")) + #endif + #if (GenerateGraph) + .AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApi")) + #endif + .AddInMemoryTokenCaches(); + #else + .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd")); + #endif + #elif (IndividualB2CAuth) + builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + #if (GenerateApi) + .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAdB2C")) + .EnableTokenAcquisitionToCallDownstreamApi() + .AddDownstreamWebApi("DownstreamApi", builder.Configuration.GetSection("DownstreamApi")) + .AddInMemoryTokenCaches(); + #else + .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAdB2C")); + #endif + #endif + #if (UseMinimalAPIs) + builder.Services.AddAuthorization(); + #endif - // Configure the HTTP request pipeline. - #if (EnableOpenAPI) - if (app.Environment.IsDevelopment()) - { - app.UseSwagger(); - app.UseSwaggerUI(); - } - #endif - #if (RequiresHttps) + #if (UseControllers) + builder.Services.AddControllers(); + #endif + #if (EnableOpenAPI) + // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle + builder.Services.AddEndpointsApiExplorer(); + builder.Services.AddSwaggerGen(); + #endif + #if (WindowsAuth) + + builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme) + .AddNegotiate(); + + builder.Services.AddAuthorization(options => + { + // By default, all incoming requests will be authorized according to the default policy. + options.FallbackPolicy = options.DefaultPolicy; + }); + #endif + + var app = builder.Build(); + + // Configure the HTTP request pipeline. + #if (EnableOpenAPI) + if (app.Environment.IsDevelopment()) + { + app.UseSwagger(); + app.UseSwaggerUI(); + } + #endif + #if (RequiresHttps) + + app.UseHttpsRedirection(); + #endif + + #if (OrganizationalAuth || IndividualAuth || WindowsAuth) + app.UseAuthentication(); + #endif + app.UseAuthorization(); + + #if (UseMinimalAPIs) + #if (OrganizationalAuth || IndividualB2CAuth) + var scopeRequiredByApi = app.Configuration["AzureAd:Scopes"] ?? ""; + #endif + var summaries = new[] + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + #if (GenerateApi) + app.MapGet("/weatherforecast", async (HttpContext httpContext, IDownstreamWebApi downstreamWebApi) => + { + httpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi); + + using var response = await downstreamWebApi.CallWebApiForUserAsync("DownstreamApi").ConfigureAwait(false); + if (response.StatusCode == System.Net.HttpStatusCode.OK) + { + var apiResult = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + // Do something + } + else + { + var error = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new HttpRequestException($"Invalid status code in the HttpResponseMessage: {response.StatusCode}: {error}"); + } + + var forecast = Enumerable.Range(1, 5).Select(index => + new WeatherForecast + { + Date = DateTime.Now.AddDays(index), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = summaries[Random.Shared.Next(summaries.Length)] + }) + .ToArray(); + + return forecast; + #elif (GenerateGraph) + app.MapGet("/weatherforecast", async (HttpContext httpContext, Graph.GraphServiceClient graphServiceClient) => + { + httpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi); + + var user = await graphServiceClient.Me.Request().GetAsync(); + + var forecast = Enumerable.Range(1, 5).Select(index => + new WeatherForecast + { + Date = DateTime.Now.AddDays(index), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = summaries[Random.Shared.Next(summaries.Length)] + }) + .ToArray(); - app.UseHttpsRedirection(); - #endif + return forecast; + #else + app.MapGet("/weatherforecast", (HttpContext httpContext) => + { + #if (OrganizationalAuth || IndividualB2CAuth) + httpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi); - #if (OrganizationalAuth || IndividualAuth || WindowsAuth) - app.UseAuthentication(); - #endif - app.UseAuthorization(); + #endif + var forecast = Enumerable.Range(1, 5).Select(index => + new WeatherForecast + { + Date = DateTime.Now.AddDays(index), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = summaries[Random.Shared.Next(summaries.Length)] + }) + .ToArray(); + return forecast; + #endif + #if (EnableOpenAPI && !NoAuth) + }) + .WithName("GetWeatherForecast") + .RequireAuthorization(); + #elif (EnableOpenAPI && NoAuth) + }) + .WithName("GetWeatherForecast"); + #elif (!EnableOpenAPI && !NoAuth) + }) + .RequireAuthorization(); + #else + }); + #endif + #endif + #if (UseControllers) - app.MapControllers(); + app.MapControllers(); + #endif - app.Run(); + app.Run(); } } diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Program.MinimalAPIs.OrgOrIndividualB2CAuth.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Program.MinimalAPIs.OrgOrIndividualB2CAuth.cs index 2329d42dafe3..c83adb9d8201 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Program.MinimalAPIs.OrgOrIndividualB2CAuth.cs +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Program.MinimalAPIs.OrgOrIndividualB2CAuth.cs @@ -64,14 +64,14 @@ app.UseAuthentication(); app.UseAuthorization(); -var scopeRequiredByApi = app.Configuration["AzureAd:Scopes"]; +var scopeRequiredByApi = app.Configuration["AzureAd:Scopes"] ?? ""; var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; #if (GenerateApi) -app.MapGet("/weatherforecast", (HttpContext httpContext, IDownstreamWebApi downstreamWebApi) => +app.MapGet("/weatherforecast", async (HttpContext httpContext, IDownstreamWebApi downstreamWebApi) => { httpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi); @@ -97,13 +97,12 @@ .ToArray(); return forecast; -}) -#elseif (GenerateGraph) -app.MapGet("/weahterforecast", (HttpContext httpContext, GraphServiceClient graphServiceClient) => +#elif (GenerateGraph) +app.MapGet("/weatherforecast", async (HttpContext httpContext, Graph.GraphServiceClient graphServiceClient) => { httpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi); - var user = await _graphServiceClient.Me.Request().GetAsync(); + var user = await graphServiceClient.Me.Request().GetAsync(); var forecast = Enumerable.Range(1, 5).Select(index => new WeatherForecast @@ -115,7 +114,6 @@ .ToArray(); return forecast; -}) #else app.MapGet("/weatherforecast", (HttpContext httpContext) => { @@ -145,4 +143,4 @@ record WeatherForecast(DateTime Date, int TemperatureC, string? Summary) { public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); -} \ No newline at end of file +} diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Program.MinimalAPIs.WindowsOrNoAuth.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Program.MinimalAPIs.WindowsOrNoAuth.cs index 8b8e2b81afd0..77eb04f48ac8 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Program.MinimalAPIs.WindowsOrNoAuth.cs +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Program.MinimalAPIs.WindowsOrNoAuth.cs @@ -37,6 +37,7 @@ #endif #if (WindowsAuth) app.UseAuthentication(); +app.UseAuthorization(); #endif var summaries = new[] diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-CSharp/Program.Main.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-CSharp/Program.Main.cs index d69747f38d51..10fac97af3ec 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-CSharp/Program.Main.cs +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-CSharp/Program.Main.cs @@ -1,6 +1,4 @@ -using Company.Application1; - -namespace Company.WebApplication1; +namespace Company.Application1; public class Program { diff --git a/src/ProjectTemplates/scripts/Run-Angular-Locally.ps1 b/src/ProjectTemplates/scripts/Run-Angular-Locally.ps1 index 95de6568eab1..30e5329e5efa 100644 --- a/src/ProjectTemplates/scripts/Run-Angular-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-Angular-Locally.ps1 @@ -9,4 +9,4 @@ $ErrorActionPreference = 'Stop' . $PSScriptRoot\Test-Template.ps1 -Test-Template "angular" "angular" "Microsoft.DotNet.Web.Spa.ProjectTemplates.6.0.6.0.0-dev.nupkg" $false +Test-Template "angular" "angular" "Microsoft.DotNet.Web.Spa.ProjectTemplates.6.0.6.0.6.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-AngularProgramMain-Locally.ps1 b/src/ProjectTemplates/scripts/Run-AngularProgramMain-Locally.ps1 index 93127bb08be0..1db52862a374 100644 --- a/src/ProjectTemplates/scripts/Run-AngularProgramMain-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-AngularProgramMain-Locally.ps1 @@ -9,4 +9,4 @@ $ErrorActionPreference = 'Stop' . $PSScriptRoot\Test-Template.ps1 -Test-Template "angular" "angular --use-program-main" "Microsoft.DotNet.Web.Spa.ProjectTemplates.6.0.6.0.0-dev.nupkg" $false +Test-Template "angular" "angular --use-program-main" "Microsoft.DotNet.Web.Spa.ProjectTemplates.6.0.6.0.6.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-Blazor-Locally.ps1 b/src/ProjectTemplates/scripts/Run-Blazor-Locally.ps1 index 7a510a7a9a45..1b0a84aaae62 100644 --- a/src/ProjectTemplates/scripts/Run-Blazor-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-Blazor-Locally.ps1 @@ -10,4 +10,4 @@ $ErrorActionPreference = 'Stop' . $PSScriptRoot\Test-Template.ps1 -Test-Template "blazorserver" "blazorserver" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.0-dev.nupkg" $false +Test-Template "blazorserver" "blazorserver" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.6.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-BlazorProgramMain-Locally.ps1 b/src/ProjectTemplates/scripts/Run-BlazorProgramMain-Locally.ps1 index 3d9fdd64a70d..c06d9f83aa80 100644 --- a/src/ProjectTemplates/scripts/Run-BlazorProgramMain-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-BlazorProgramMain-Locally.ps1 @@ -10,4 +10,4 @@ $ErrorActionPreference = 'Stop' . $PSScriptRoot\Test-Template.ps1 -Test-Template "blazorserver" "blazorserver --use-program-main" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.0-dev.nupkg" $false +Test-Template "blazorserver" "blazorserver --use-program-main" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.6.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-BlazorWasm-Locally.ps1 b/src/ProjectTemplates/scripts/Run-BlazorWasm-Locally.ps1 index 50f70bb1b04d..5945a9fd00bd 100644 --- a/src/ProjectTemplates/scripts/Run-BlazorWasm-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-BlazorWasm-Locally.ps1 @@ -10,4 +10,4 @@ $ErrorActionPreference = 'Stop' . $PSScriptRoot\Test-Template.ps1 -Test-Template "blazorwasm" "blazorwasm --hosted --auth Individual" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.0-dev.nupkg" $true +Test-Template "blazorwasm" "blazorwasm --hosted --auth Individual" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.6.nupkg" $true diff --git a/src/ProjectTemplates/scripts/Run-BlazorWasmProgramMain-Locally.ps1 b/src/ProjectTemplates/scripts/Run-BlazorWasmProgramMain-Locally.ps1 index 7c8755a8bba3..d7541fe5505f 100644 --- a/src/ProjectTemplates/scripts/Run-BlazorWasmProgramMain-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-BlazorWasmProgramMain-Locally.ps1 @@ -10,4 +10,4 @@ $ErrorActionPreference = 'Stop' . $PSScriptRoot\Test-Template.ps1 -Test-Template "blazorwasm" "blazorwasm --use-program-main --hosted --auth Individual" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.0-dev.nupkg" $true +Test-Template "blazorwasm" "blazorwasm --use-program-main --hosted --auth Individual" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.6.nupkg" $true diff --git a/src/ProjectTemplates/scripts/Run-EmptyWeb-Locally.ps1 b/src/ProjectTemplates/scripts/Run-EmptyWeb-Locally.ps1 index 10e743504b58..ad97e7ed4e3e 100644 --- a/src/ProjectTemplates/scripts/Run-EmptyWeb-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-EmptyWeb-Locally.ps1 @@ -9,4 +9,4 @@ $ErrorActionPreference = 'Stop' . $PSScriptRoot\Test-Template.ps1 -Test-Template "web" "web" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.0-dev.nupkg" $false +Test-Template "web" "web" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.6.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-EmptyWebProgramMain-Locally.ps1 b/src/ProjectTemplates/scripts/Run-EmptyWebProgramMain-Locally.ps1 index 7453063baf21..d2a4b5f55bf1 100644 --- a/src/ProjectTemplates/scripts/Run-EmptyWebProgramMain-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-EmptyWebProgramMain-Locally.ps1 @@ -9,4 +9,4 @@ $ErrorActionPreference = 'Stop' . $PSScriptRoot\Test-Template.ps1 -Test-Template "web" "web --use-program-main" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.0-dev.nupkg" $false +Test-Template "web" "web --use-program-main" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.6.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-Razor-Locally.ps1 b/src/ProjectTemplates/scripts/Run-Razor-Locally.ps1 index 6f16590be8fe..8d562fe763c2 100644 --- a/src/ProjectTemplates/scripts/Run-Razor-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-Razor-Locally.ps1 @@ -6,4 +6,4 @@ param() . $PSScriptRoot\Test-Template.ps1 -Test-Template "webapp" "webapp -au Individual" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.0-dev.nupkg" $false +Test-Template "webapp" "webapp -au Individual" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.6.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-RazorProgramMain-Locally.ps1 b/src/ProjectTemplates/scripts/Run-RazorProgramMain-Locally.ps1 index 4224cf985dd2..bfc7748ccdd4 100644 --- a/src/ProjectTemplates/scripts/Run-RazorProgramMain-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-RazorProgramMain-Locally.ps1 @@ -6,4 +6,4 @@ param() . $PSScriptRoot\Test-Template.ps1 -Test-Template "webapp" "webapp -au Individual --use-program-main" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.0-dev.nupkg" $false +Test-Template "webapp" "webapp -au Individual --use-program-main" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.6.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-React-Locally.ps1 b/src/ProjectTemplates/scripts/Run-React-Locally.ps1 index a4baf0c40568..c09e8c1fc624 100644 --- a/src/ProjectTemplates/scripts/Run-React-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-React-Locally.ps1 @@ -9,4 +9,4 @@ $ErrorActionPreference = 'Stop' . $PSScriptRoot\Test-Template.ps1 -Test-Template "react" "react" "Microsoft.DotNet.Web.Spa.ProjectTemplates.6.0.6.0.0-dev.nupkg" $false +Test-Template "react" "react" "Microsoft.DotNet.Web.Spa.ProjectTemplates.6.0.6.0.6.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-ReactProgramMain-Locally.ps1 b/src/ProjectTemplates/scripts/Run-ReactProgramMain-Locally.ps1 index df61a5a11740..b05f556fc17f 100644 --- a/src/ProjectTemplates/scripts/Run-ReactProgramMain-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-ReactProgramMain-Locally.ps1 @@ -9,4 +9,4 @@ $ErrorActionPreference = 'Stop' . $PSScriptRoot\Test-Template.ps1 -Test-Template "react" "react --use-program-main" "Microsoft.DotNet.Web.Spa.ProjectTemplates.6.0.6.0.0-dev.nupkg" $false +Test-Template "react" "react --use-program-main" "Microsoft.DotNet.Web.Spa.ProjectTemplates.6.0.6.0.6.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-ReactRedux-Locally.ps1 b/src/ProjectTemplates/scripts/Run-ReactRedux-Locally.ps1 index aedd8ec88acb..df02bed62438 100644 --- a/src/ProjectTemplates/scripts/Run-ReactRedux-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-ReactRedux-Locally.ps1 @@ -9,4 +9,4 @@ $ErrorActionPreference = 'Stop' . $PSScriptRoot\Test-Template.ps1 -Test-Template "reactredux" "reactredux" "Microsoft.DotNet.Web.Spa.ProjectTemplates.6.0.6.0.0-dev.nupkg" $false +Test-Template "reactredux" "reactredux" "Microsoft.DotNet.Web.Spa.ProjectTemplates.6.0.6.0.6.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-Starterweb-Locally.ps1 b/src/ProjectTemplates/scripts/Run-Starterweb-Locally.ps1 index ab195cd016ef..6d6afa43a39c 100644 --- a/src/ProjectTemplates/scripts/Run-Starterweb-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-Starterweb-Locally.ps1 @@ -9,4 +9,4 @@ $ErrorActionPreference = 'Stop' . $PSScriptRoot\Test-Template.ps1 -Test-Template "mvc" "mvc -au Individual" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.0-dev.nupkg" $false +Test-Template "mvc" "mvc -au Individual" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.6.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-StarterwebProgramMain-Locally.ps1 b/src/ProjectTemplates/scripts/Run-StarterwebProgramMain-Locally.ps1 index 076106d3e861..4965829b3904 100644 --- a/src/ProjectTemplates/scripts/Run-StarterwebProgramMain-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-StarterwebProgramMain-Locally.ps1 @@ -9,4 +9,4 @@ $ErrorActionPreference = 'Stop' . $PSScriptRoot\Test-Template.ps1 -Test-Template "mvc" "mvc -au Individual --use-program-main" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.0-dev.nupkg" $false +Test-Template "mvc" "mvc -au Individual --use-program-main" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.6.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-WebApi-Locally.ps1 b/src/ProjectTemplates/scripts/Run-WebApi-Locally.ps1 index 467d6e57e76e..cd866e4e355e 100644 --- a/src/ProjectTemplates/scripts/Run-WebApi-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-WebApi-Locally.ps1 @@ -9,4 +9,4 @@ $ErrorActionPreference = 'Stop' . $PSScriptRoot\Test-Template.ps1 -Test-Template "webapi" "webapi" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.0-dev.nupkg" $false +Test-Template "webapi" "webapi" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.6.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-WebApiMinimal-Locally.ps1 b/src/ProjectTemplates/scripts/Run-WebApiMinimal-Locally.ps1 index 19325a7c7eca..5afe6c0d586f 100644 --- a/src/ProjectTemplates/scripts/Run-WebApiMinimal-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-WebApiMinimal-Locally.ps1 @@ -9,4 +9,4 @@ $ErrorActionPreference = 'Stop' . $PSScriptRoot\Test-Template.ps1 -Test-Template "webapimin" "webapi -minimal" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.0-dev.nupkg" $false +Test-Template "webapimin" "webapi -minimal" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.6.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-WebApiProgamMain-Locally.ps1 b/src/ProjectTemplates/scripts/Run-WebApiProgamMain-Locally.ps1 index 41f794b7eaaf..045d249e9aa3 100644 --- a/src/ProjectTemplates/scripts/Run-WebApiProgamMain-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-WebApiProgamMain-Locally.ps1 @@ -9,4 +9,4 @@ $ErrorActionPreference = 'Stop' . $PSScriptRoot\Test-Template.ps1 -Test-Template "webapi" "webapi --use-program-main" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.0-dev.nupkg" $false +Test-Template "webapi" "webapi --use-program-main" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.6.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-WebApiProgamMainMinimal-Locally.ps1 b/src/ProjectTemplates/scripts/Run-WebApiProgamMainMinimal-Locally.ps1 new file mode 100644 index 000000000000..c8b66bdcc0ad --- /dev/null +++ b/src/ProjectTemplates/scripts/Run-WebApiProgamMainMinimal-Locally.ps1 @@ -0,0 +1,12 @@ +#!/usr/bin/env pwsh +#requires -version 4 + +[CmdletBinding(PositionalBinding = $false)] +param() + +Set-StrictMode -Version 2 +$ErrorActionPreference = 'Stop' + +. $PSScriptRoot\Test-Template.ps1 + +Test-Template "webapi" "webapi --use-program-main --use-minimal-apis" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.6.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-Worker-Locally.ps1 b/src/ProjectTemplates/scripts/Run-Worker-Locally.ps1 index 9f3c0272096d..c201a4fa2e73 100644 --- a/src/ProjectTemplates/scripts/Run-Worker-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-Worker-Locally.ps1 @@ -9,4 +9,4 @@ $ErrorActionPreference = 'Stop' . $PSScriptRoot\Test-Template.ps1 -Test-Template "worker" "worker" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.0-dev.nupkg" $false +Test-Template "worker" "worker" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.6.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-WorkerProgramMain-Locally.ps1 b/src/ProjectTemplates/scripts/Run-WorkerProgramMain-Locally.ps1 index 9e0aa3d4607b..2852882312c5 100644 --- a/src/ProjectTemplates/scripts/Run-WorkerProgramMain-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-WorkerProgramMain-Locally.ps1 @@ -9,4 +9,4 @@ $ErrorActionPreference = 'Stop' . $PSScriptRoot\Test-Template.ps1 -Test-Template "worker" "worker --use-program-main" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.0-dev.nupkg" $false +Test-Template "worker" "worker --use-program-main" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.6.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-gRPC-Locally.ps1 b/src/ProjectTemplates/scripts/Run-gRPC-Locally.ps1 index 2803a34d9c9c..21950ac78920 100644 --- a/src/ProjectTemplates/scripts/Run-gRPC-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-gRPC-Locally.ps1 @@ -9,4 +9,4 @@ $ErrorActionPreference = 'Stop' . $PSScriptRoot\Test-Template.ps1 -Test-Template "grpc" "grpc" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.0-dev.nupkg" $false +Test-Template "grpc" "grpc" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.6.nupkg" $false diff --git a/src/ProjectTemplates/test/ArgConstants.cs b/src/ProjectTemplates/test/ArgConstants.cs new file mode 100644 index 000000000000..94a3b2965cce --- /dev/null +++ b/src/ProjectTemplates/test/ArgConstants.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Templates.Test; + +internal static class ArgConstants +{ + public const string UseProgramMain = "--use-program-main"; + public const string UseMinimalApis = "--use-minimal-apis"; + public const string Hosted = "--hosted"; + public const string Pwa = "--pwa"; + public const string CallsGraph = "--calls-graph"; + public const string CalledApiUrl = "--called-api-url"; + public const string CalledApiUrlGraphMicrosoftCom = "--called-api-url \"https://graph.microsoft.com\""; + public const string CalledApiScopes = "--called-api-scopes"; + public const string CalledApiScopesUserReadWrite = $"{CalledApiScopes} user.readwrite"; + public const string NoOpenApi = "--no-openapi"; + public const string Auth = "-au"; + public const string ClientId = "--client-id"; + public const string Domain = "--domain"; + public const string DefaultScope = "--default-scope"; + public const string AppIdUri = "--app-id-uri"; + public const string AppIdClientId = "--api-client-id"; + public const string TenantId = "--tenant-id"; + public const string AadB2cInstance = "--aad-b2c-instance"; + public const string UseLocalDb = "-uld"; +} diff --git a/src/ProjectTemplates/test/AssemblyInfo.AssemblyFixtures.cs b/src/ProjectTemplates/test/AssemblyInfo.AssemblyFixtures.cs index 7e531ec57ea6..3422eecd8c53 100644 --- a/src/ProjectTemplates/test/AssemblyInfo.AssemblyFixtures.cs +++ b/src/ProjectTemplates/test/AssemblyInfo.AssemblyFixtures.cs @@ -3,5 +3,7 @@ using Microsoft.AspNetCore.Testing; using Templates.Test.Helpers; +using Xunit; [assembly: AssemblyFixture(typeof(ProjectFactoryFixture))] +[assembly: CollectionBehavior(DisableTestParallelization = true)] \ No newline at end of file diff --git a/src/ProjectTemplates/test/BaselineTest.cs b/src/ProjectTemplates/test/BaselineTest.cs index 786b7d36dc6b..f98f69b4f0d7 100644 --- a/src/ProjectTemplates/test/BaselineTest.cs +++ b/src/ProjectTemplates/test/BaselineTest.cs @@ -67,11 +67,12 @@ public ITestOutputHelper Output } // This test should generally not be quarantined as it only is checking that the expected files are on disk + // and that the namespace declarations in the generated .cs files start with the project name [Theory] [MemberData(nameof(TemplateBaselines))] public async Task Template_Produces_The_Right_Set_Of_FilesAsync(string arguments, string[] expectedFiles) { - Project = await ProjectFactory.GetOrCreateProject(CreateProjectKey(arguments), Output); + Project = await ProjectFactory.CreateProject(Output); var createResult = await Project.RunDotNetNewRawAsync(arguments); Assert.True(createResult.ExitCode == 0, createResult.GetFormattedOutput()); @@ -100,66 +101,22 @@ public async Task Template_Produces_The_Right_Set_Of_FilesAsync(string arguments continue; } Assert.Contains(relativePath, expectedFiles); - } - } - - private static ConcurrentDictionary _projectKeys = new(); - - private string CreateProjectKey(string arguments) - { - var text = "baseline"; - - // Turn string like "new templatename -minimal -au SingleOrg --another-option OptionValue" - // into array like [ "new templatename", "minimal", "au SingleOrg", "another-option OptionValue" ] - var argumentsArray = arguments - .Split(new[] { " --", " -" }, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries) - .ToArray(); - // Add template name, value has form of "new name" - text += argumentsArray[0].Substring("new ".Length); - - // Sort arguments to ensure definitions that differ only by arguments order are caught - Array.Sort(argumentsArray, StringComparer.Ordinal); - - foreach (var argValue in argumentsArray) - { - var argSegments = argValue.Split(' ', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); - - if (argSegments.Length == 0) - { - continue; - } - else if (argSegments.Length == 1) - { - text += argSegments[0] switch - { - "ho" => "hosted", - "p" => "pwa", - _ => argSegments[0].Replace("-","") - }; - } - else + // Commented out to see if it impacts the Helix test failures + if (relativePath.EndsWith(".cs", StringComparison.Ordinal)) { - text += argSegments[0] switch + var namespaceDeclarationPrefix = "namespace "; + var namespaceDeclaration = File.ReadLines(file) + .SingleOrDefault(line => line.StartsWith(namespaceDeclarationPrefix, StringComparison.Ordinal)) + ?.Substring(namespaceDeclarationPrefix.Length); + + // nullable because Program.cs with top-level statements doesn't have a namespace declaration + if (namespaceDeclaration is not null) { - "au" => argSegments[1], - "uld" => "uld", - "language" => argSegments[1].Replace("#", "Sharp"), - "support-pages-and-views" when argSegments[1] == "true" => "supportpagesandviewstrue", - _ => "" - }; + Assert.StartsWith(Project.ProjectName, namespaceDeclaration, StringComparison.Ordinal); + } } } - - if (!_projectKeys.TryAdd(text, null)) - { - throw new InvalidOperationException( - $"Project key for template with args '{arguments}' already exists. " + - $"Check that the metadata specified in {BaselineDefinitionFileResourceName} is correct and that " + - $"the {nameof(CreateProjectKey)} method is considering enough template arguments to ensure uniqueness."); - } - - return text; } private void AssertFileExists(string basePath, string path, bool shouldExist) diff --git a/src/ProjectTemplates/test/BlazorServerTemplateTest.cs b/src/ProjectTemplates/test/BlazorServerTemplateTest.cs index 104f2304020d..6f277abd3a74 100644 --- a/src/ProjectTemplates/test/BlazorServerTemplateTest.cs +++ b/src/ProjectTemplates/test/BlazorServerTemplateTest.cs @@ -24,30 +24,37 @@ public BlazorServerTemplateTest(ProjectFactoryFixture projectFactory) public override string ProjectType { get; } = "blazorserver"; [Fact] - public Task BlazorServerTemplateWorks_NoAuth() => CreateBuildPublishAsync("blazorservernoauth"); + public Task BlazorServerTemplateWorks_NoAuth() => CreateBuildPublishAsync(); [Fact] - public Task BlazorServerTemplateWorks_ProgamMainNoAuth() => CreateBuildPublishAsync("blazorservernoauth", args: new [] { "--use-program-main" }); + public Task BlazorServerTemplateWorks_ProgamMainNoAuth() => CreateBuildPublishAsync(args: new [] { ArgConstants.UseProgramMain }); - [Theory] - [InlineData(true, null)] - [InlineData(true, new string[] { "--use-program-main" })] - [InlineData(false, null)] - [InlineData(false, new string[] { "--use-program-main" })] + [ConditionalTheory] + [InlineData("Individual", null)] + [InlineData("Individual", new string[] { ArgConstants.UseProgramMain })] [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/30825", Queues = "All.OSX")] - public Task BlazorServerTemplateWorks_IndividualAuth(bool useLocalDB, string[] args) => CreateBuildPublishAsync("blazorserverindividual" + (useLocalDB ? "uld" : "", args: args)); + public Task BlazorServerTemplateWorks_IndividualAuth(string auth, string[] args) => CreateBuildPublishAsync(auth, args: args); + + [ConditionalTheory] + [InlineData("Individual", new string[] { ArgConstants.UseLocalDb })] + [InlineData("Individual", new string[] { ArgConstants.UseProgramMain, ArgConstants.UseLocalDb })] + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX, SkipReason = "No LocalDb on non-Windows")] + public Task BlazorServerTemplateWorks_IndividualAuth_LocalDb(string auth, string[] args) => CreateBuildPublishAsync(auth, args: args); [Theory] [InlineData("IndividualB2C", null)] - [InlineData("IndividualB2C", new string[] { "--called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })] - [InlineData("IndividualB2C", new string[] { "--use-program-main", "--called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })] - [InlineData("SingleOrg", null)] - [InlineData("SingleOrg", new string[] { "--called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })] - [InlineData("SingleOrg", new string[] { "--use-program-main --called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })] - [InlineData("SingleOrg", new string[] { "--calls-graph" })] - [InlineData("SingleOrg", new string[] { "--use-program-main --calls-graph" })] - public Task BlazorServerTemplate_IdentityWeb_BuildAndPublish(string auth, string[] args) - => CreateBuildPublishAsync("blazorserveridweb" + Guid.NewGuid().ToString().Substring(0, 10).ToLowerInvariant(), auth, args); + [InlineData("IndividualB2C", new[] { ArgConstants.UseProgramMain })] + [InlineData("IndividualB2C", new[] { ArgConstants.CalledApiUrlGraphMicrosoftCom, ArgConstants.CalledApiScopesUserReadWrite })] + [InlineData("IndividualB2C", new[] { ArgConstants.UseProgramMain, ArgConstants.CalledApiUrlGraphMicrosoftCom, ArgConstants.CalledApiScopesUserReadWrite })] + public Task BlazorServerTemplate_IdentityWeb_BuildAndPublish_IndividualB2C(string auth, string[] args) => CreateBuildPublishAsync(auth, args); + [Theory] + [InlineData("SingleOrg", null)] + [InlineData("SingleOrg", new[] { ArgConstants.UseProgramMain })] + [InlineData("SingleOrg", new[] { ArgConstants.CalledApiUrlGraphMicrosoftCom, ArgConstants.CalledApiScopesUserReadWrite })] + [InlineData("SingleOrg", new[] { ArgConstants.UseProgramMain, ArgConstants.CalledApiUrlGraphMicrosoftCom, ArgConstants.CalledApiScopesUserReadWrite })] + [InlineData("SingleOrg", new[] { ArgConstants.CallsGraph })] + [InlineData("SingleOrg", new[] { ArgConstants.UseProgramMain, ArgConstants.CallsGraph })] + public Task BlazorServerTemplate_IdentityWeb_BuildAndPublish_SingleOrg(string auth, string[] args) => CreateBuildPublishAsync(auth, args); } } diff --git a/src/ProjectTemplates/test/BlazorTemplateTest.cs b/src/ProjectTemplates/test/BlazorTemplateTest.cs index 792e9fd24750..b6096de7ce63 100644 --- a/src/ProjectTemplates/test/BlazorTemplateTest.cs +++ b/src/ProjectTemplates/test/BlazorTemplateTest.cs @@ -41,12 +41,12 @@ public ITestOutputHelper Output public abstract string ProjectType { get; } - protected async Task CreateBuildPublishAsync(string projectName, string auth = null, string[] args = null, string targetFramework = null, bool serverProject = false, bool onlyCreate = false) + protected async Task CreateBuildPublishAsync(string auth = null, string[] args = null, string targetFramework = null, bool serverProject = false, bool onlyCreate = false) { // Additional arguments are needed. See: https://github.com/dotnet/aspnetcore/issues/24278 Environment.SetEnvironmentVariable("EnableDefaultScopedCssItems", "true"); - var project = await ProjectFactory.GetOrCreateProject(projectName, Output); + var project = await ProjectFactory.CreateProject(Output); if (targetFramework != null) { project.TargetFramework = targetFramework; diff --git a/src/ProjectTemplates/test/BlazorWasmTemplateTest.cs b/src/ProjectTemplates/test/BlazorWasmTemplateTest.cs index dae6148d213a..fc9bfa5f8e4a 100644 --- a/src/ProjectTemplates/test/BlazorWasmTemplateTest.cs +++ b/src/ProjectTemplates/test/BlazorWasmTemplateTest.cs @@ -27,7 +27,7 @@ public BlazorWasmTemplateTest(ProjectFactoryFixture projectFactory) [Fact] public async Task BlazorWasmStandaloneTemplateCanCreateBuildPublish() { - var project = await CreateBuildPublishAsync("blazorstandalone"); + var project = await CreateBuildPublishAsync(); // The service worker assets manifest isn't generated for non-PWA projects var publishDir = Path.Combine(project.TemplatePublishDir, "wwwroot"); @@ -35,18 +35,18 @@ public async Task BlazorWasmStandaloneTemplateCanCreateBuildPublish() } [Fact] - public Task BlazorWasmHostedTemplateCanCreateBuildPublish() => CreateBuildPublishAsync("blazorhosted", args: new[] { "--hosted" }, serverProject: true); + public Task BlazorWasmHostedTemplateCanCreateBuildPublish() => CreateBuildPublishAsync(args: new[] { ArgConstants.Hosted }, serverProject: true); [Fact] - public Task BlazorWasmHostedTemplateWithProgamMainCanCreateBuildPublish() => CreateBuildPublishAsync("blazorhosted", args: new[] { "--use-program-main", "--hosted" }, serverProject: true); + public Task BlazorWasmHostedTemplateWithProgamMainCanCreateBuildPublish() => CreateBuildPublishAsync(args: new[] { ArgConstants.UseProgramMain, ArgConstants.Hosted }, serverProject: true); [Fact] - public Task BlazorWasmStandalonePwaTemplateCanCreateBuildPublish() => CreateBuildPublishAsync("blazorstandalonepwa", args: new[] { "--pwa" }); + public Task BlazorWasmStandalonePwaTemplateCanCreateBuildPublish() => CreateBuildPublishAsync(args: new[] { ArgConstants.Pwa }); [Fact] public async Task BlazorWasmHostedPwaTemplateCanCreateBuildPublish() { - var project = await CreateBuildPublishAsync("blazorhostedpwa", args: new[] { "--hosted", "--pwa" }, serverProject: true); + var project = await CreateBuildPublishAsync(args: new[] { ArgConstants.Hosted, ArgConstants.Pwa }, serverProject: true); var serverProject = GetSubProject(project, "Server", $"{project.ProjectName}.Server"); @@ -81,20 +81,32 @@ private void ValidatePublishedServiceWorker(Project project) // LocalDB doesn't work on non Windows platforms [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] public Task BlazorWasmHostedTemplate_IndividualAuth_Works_WithLocalDB() - => BlazorWasmHostedTemplate_IndividualAuth_Works(true); + => BlazorWasmHostedTemplate_IndividualAuth_Works(true, false); [ConditionalFact] [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/34554", Queues = "Windows.10.Arm64v8.Open")] public Task BlazorWasmHostedTemplate_IndividualAuth_Works_WithOutLocalDB() - => BlazorWasmHostedTemplate_IndividualAuth_Works(false); + => BlazorWasmHostedTemplate_IndividualAuth_Works(false, false); - private async Task CreateBuildPublishIndividualAuthProject(bool useLocalDb) + [ConditionalFact] + [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/34554", Queues = "Windows.10.Arm64v8.Open")] + // LocalDB doesn't work on non Windows platforms + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] + public Task BlazorWasmHostedTemplate_IndividualAuth_Works_WithLocalDB_ProgramMain() + => BlazorWasmHostedTemplate_IndividualAuth_Works(true, true); + + [ConditionalFact] + [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/34554", Queues = "Windows.10.Arm64v8.Open")] + public Task BlazorWasmHostedTemplate_IndividualAuth_Works_WithOutLocalDB_ProgramMain() + => BlazorWasmHostedTemplate_IndividualAuth_Works(false, true); + + private async Task CreateBuildPublishIndividualAuthProject(bool useLocalDb, bool useProgramMain = false) { // Additional arguments are needed. See: https://github.com/dotnet/aspnetcore/issues/24278 Environment.SetEnvironmentVariable("EnableDefaultScopedCssItems", "true"); - var project = await CreateBuildPublishAsync("blazorhostedindividual" + (useLocalDb ? "uld" : ""), - args: new[] { "--hosted", "-au", "Individual", useLocalDb ? "-uld" : "" }); + var project = await CreateBuildPublishAsync("Individual", + args: new[] { ArgConstants.Hosted, useLocalDb ? ArgConstants.UseLocalDb : "", useProgramMain ? ArgConstants.UseProgramMain : "" }); var serverProject = GetSubProject(project, "Server", $"{project.ProjectName}.Server"); @@ -128,9 +140,9 @@ private async Task CreateBuildPublishIndividualAuthProject(bool useLoca return project; } - private async Task BlazorWasmHostedTemplate_IndividualAuth_Works(bool useLocalDb) + private async Task BlazorWasmHostedTemplate_IndividualAuth_Works(bool useLocalDb, bool useProgramMain) { - var project = await CreateBuildPublishIndividualAuthProject(useLocalDb: useLocalDb); + var project = await CreateBuildPublishIndividualAuthProject(useLocalDb: useLocalDb, useProgramMain: useProgramMain); var serverProject = GetSubProject(project, "Server", $"{project.ProjectName}.Server"); } @@ -138,71 +150,136 @@ private async Task BlazorWasmHostedTemplate_IndividualAuth_Works(bool useLocalDb [Fact] public async Task BlazorWasmStandaloneTemplate_IndividualAuth_CreateBuildPublish() { - var project = await CreateBuildPublishAsync("blazorstandaloneindividual", args: new[] { - "-au", - "Individual", + var project = await CreateBuildPublishAsync("Individual", args: new[] { "--authority", "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration", - "--client-id", + ArgConstants.ClientId, "sample-client-id" }); } - public static TheoryData TemplateData => new TheoryData + public static TheoryData TemplateDataIndividualB2C => new TheoryData { new TemplateInstance( "blazorwasmhostedaadb2c", "-ho", - "-au", "IndividualB2C", - "--aad-b2c-instance", "example.b2clogin.com", + ArgConstants.Auth, "IndividualB2C", + ArgConstants.AadB2cInstance, "example.b2clogin.com", "-ssp", "b2c_1_siupin", - "--client-id", "clientId", - "--domain", "my-domain", - "--default-scope", "full", - "--app-id-uri", "ApiUri", - "--api-client-id", "1234123413241324"), + ArgConstants.ClientId, "clientId", + ArgConstants.Domain, "my-domain", + ArgConstants.DefaultScope, "full", + ArgConstants.AppIdUri, "ApiUri", + ArgConstants.AppIdClientId, "1234123413241324"), + new TemplateInstance( + "blazorwasmhostedaadb2c_program_main", "-ho", + ArgConstants.Auth, "IndividualB2C", + ArgConstants.AadB2cInstance, "example.b2clogin.com", + "-ssp", "b2c_1_siupin", + ArgConstants.ClientId, "clientId", + ArgConstants.Domain, "my-domain", + ArgConstants.DefaultScope, "full", + ArgConstants.AppIdUri, "ApiUri", + ArgConstants.AppIdClientId, "1234123413241324", + ArgConstants.UseProgramMain), + new TemplateInstance( + "blazorwasmstandaloneaadb2c", + ArgConstants.Auth, "IndividualB2C", + ArgConstants.AadB2cInstance, "example.b2clogin.com", + "-ssp", "b2c_1_siupin", + ArgConstants.ClientId, "clientId", + ArgConstants.Domain, "my-domain"), + new TemplateInstance( + "blazorwasmstandaloneaadb2c_program_main", + ArgConstants.Auth, "IndividualB2C", + ArgConstants.AadB2cInstance, "example.b2clogin.com", + "-ssp", "b2c_1_siupin", + ArgConstants.ClientId, "clientId", + ArgConstants.Domain, "my-domain", + ArgConstants.UseProgramMain), + }; + + public static TheoryData TemplateDataSingleOrg => new TheoryData + { new TemplateInstance( "blazorwasmhostedaad", "-ho", - "-au", "SingleOrg", - "--domain", "my-domain", - "--tenant-id", "tenantId", - "--client-id", "clientId", - "--default-scope", "full", - "--app-id-uri", "ApiUri", - "--api-client-id", "1234123413241324"), + ArgConstants.Auth, "SingleOrg", + ArgConstants.Domain, "my-domain", + ArgConstants.TenantId, "tenantId", + ArgConstants.ClientId, "clientId", + ArgConstants.DefaultScope, "full", + ArgConstants.AppIdUri, "ApiUri", + ArgConstants.AppIdClientId, "1234123413241324"), new TemplateInstance( "blazorwasmhostedaadgraph", "-ho", - "-au", "SingleOrg", - "--calls-graph", - "--domain", "my-domain", - "--tenant-id", "tenantId", - "--client-id", "clientId", - "--default-scope", "full", - "--app-id-uri", "ApiUri", - "--api-client-id", "1234123413241324"), + ArgConstants.Auth, "SingleOrg", + ArgConstants.CallsGraph, + ArgConstants.Domain, "my-domain", + ArgConstants.TenantId, "tenantId", + ArgConstants.ClientId, "clientId", + ArgConstants.DefaultScope, "full", + ArgConstants.AppIdUri, "ApiUri", + ArgConstants.AppIdClientId, "1234123413241324"), new TemplateInstance( "blazorwasmhostedaadapi", "-ho", - "-au", "SingleOrg", - "--called-api-url", "\"https://graph.microsoft.com\"", - "--called-api-scopes", "user.readwrite", - "--domain", "my-domain", - "--tenant-id", "tenantId", - "--client-id", "clientId", - "--default-scope", "full", - "--app-id-uri", "ApiUri", - "--api-client-id", "1234123413241324"), - new TemplateInstance( - "blazorwasmstandaloneaadb2c", - "-au", "IndividualB2C", - "--aad-b2c-instance", "example.b2clogin.com", - "-ssp", "b2c_1_siupin", - "--client-id", "clientId", - "--domain", "my-domain"), + ArgConstants.Auth, "SingleOrg", + ArgConstants.CalledApiUrl, "\"https://graph.microsoft.com\"", + ArgConstants.CalledApiScopes, "user.readwrite", + ArgConstants.Domain, "my-domain", + ArgConstants.TenantId, "tenantId", + ArgConstants.ClientId, "clientId", + ArgConstants.DefaultScope, "full", + ArgConstants.AppIdUri, "ApiUri", + ArgConstants.AppIdClientId, "1234123413241324"), new TemplateInstance( "blazorwasmstandaloneaad", - "-au", "SingleOrg", - "--domain", "my-domain", - "--tenant-id", "tenantId", - "--client-id", "clientId"), + ArgConstants.Auth, "SingleOrg", + ArgConstants.Domain, "my-domain", + ArgConstants.TenantId, "tenantId", + ArgConstants.ClientId, "clientId"), + }; + + public static TheoryData TemplateDataSingleOrgProgramMain => new TheoryData + { + new TemplateInstance( + "blazorwasmhostedaad_program_main", "-ho", + ArgConstants.Auth, "SingleOrg", + ArgConstants.Domain, "my-domain", + ArgConstants.TenantId, "tenantId", + ArgConstants.ClientId, "clientId", + ArgConstants.DefaultScope, "full", + ArgConstants.AppIdUri, "ApiUri", + ArgConstants.AppIdClientId, "1234123413241324", + ArgConstants.UseProgramMain), + new TemplateInstance( + "blazorwasmhostedaadgraph_program_main", "-ho", + ArgConstants.Auth, "SingleOrg", + ArgConstants.CallsGraph, + ArgConstants.Domain, "my-domain", + ArgConstants.TenantId, "tenantId", + ArgConstants.ClientId, "clientId", + ArgConstants.DefaultScope, "full", + ArgConstants.AppIdUri, "ApiUri", + ArgConstants.AppIdClientId, "1234123413241324", + ArgConstants.UseProgramMain), + new TemplateInstance( + "blazorwasmhostedaadapi_program_main", "-ho", + ArgConstants.Auth, "SingleOrg", + ArgConstants.CalledApiUrl, "\"https://graph.microsoft.com\"", + ArgConstants.CalledApiScopes, "user.readwrite", + ArgConstants.Domain, "my-domain", + ArgConstants.TenantId, "tenantId", + ArgConstants.ClientId, "clientId", + ArgConstants.DefaultScope, "full", + ArgConstants.AppIdUri, "ApiUri", + ArgConstants.AppIdClientId, "1234123413241324", + ArgConstants.UseProgramMain), + new TemplateInstance( + "blazorwasmstandaloneaad_program_main", + ArgConstants.Auth, "SingleOrg", + ArgConstants.Domain, "my-domain", + ArgConstants.TenantId, "tenantId", + ArgConstants.ClientId, "clientId", + ArgConstants.UseProgramMain), }; public class TemplateInstance @@ -218,9 +295,19 @@ public TemplateInstance(string name, params string[] arguments) } [Theory] - [MemberData(nameof(TemplateData))] - public Task BlazorWasmHostedTemplate_AzureActiveDirectoryTemplate_Works(TemplateInstance instance) - => CreateBuildPublishAsync(instance.Name, args: instance.Arguments, targetFramework: "netstandard2.1"); + [MemberData(nameof(TemplateDataIndividualB2C))] + public Task BlazorWasmHostedTemplate_AzureActiveDirectoryTemplate_IndividualB2C_Works(TemplateInstance instance) + => CreateBuildPublishAsync(args: instance.Arguments, targetFramework: "netstandard2.1"); + + [Theory] + [MemberData(nameof(TemplateDataSingleOrg))] + public Task BlazorWasmHostedTemplate_AzureActiveDirectoryTemplate_SingleOrg_Works(TemplateInstance instance) + => CreateBuildPublishAsync(args: instance.Arguments, targetFramework: "netstandard2.1"); + + [Theory] + [MemberData(nameof(TemplateDataSingleOrgProgramMain))] + public Task BlazorWasmHostedTemplate_AzureActiveDirectoryTemplate_SingleOrg_ProgramMain_Works(TemplateInstance instance) + => CreateBuildPublishAsync(args: instance.Arguments, targetFramework: "netstandard2.1"); private string ReadFile(string basePath, string path) { diff --git a/src/ProjectTemplates/test/EmptyWebTemplateTest.cs b/src/ProjectTemplates/test/EmptyWebTemplateTest.cs index 7468a587b41e..e350f65cb583 100644 --- a/src/ProjectTemplates/test/EmptyWebTemplateTest.cs +++ b/src/ProjectTemplates/test/EmptyWebTemplateTest.cs @@ -42,7 +42,7 @@ public async Task EmptyWebTemplateCSharp() [SkipOnHelix("Cert failure, https://github.com/dotnet/aspnetcore/issues/28090", Queues = "All.OSX;" + HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] public async Task EmptyWebTemplateProgramMainCSharp() { - await EmtpyTemplateCore(languageOverride: null, args: new [] { "--use-program-main" }); + await EmtpyTemplateCore(languageOverride: null, args: new[] { ArgConstants.UseProgramMain }); } [Fact] @@ -53,7 +53,7 @@ public async Task EmptyWebTemplateFSharp() private async Task EmtpyTemplateCore(string languageOverride, string[] args = null) { - var project = await ProjectFactory.GetOrCreateProject("empty" + (languageOverride == "F#" ? "fsharp" : "csharp"), Output); + var project = await ProjectFactory.CreateProject(Output); var createResult = await project.RunDotNetNewAsync("web", args: args, language: languageOverride); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); diff --git a/src/ProjectTemplates/test/GrpcTemplateTest.cs b/src/ProjectTemplates/test/GrpcTemplateTest.cs index d26d0f8623a6..128ca22f4251 100644 --- a/src/ProjectTemplates/test/GrpcTemplateTest.cs +++ b/src/ProjectTemplates/test/GrpcTemplateTest.cs @@ -41,9 +41,9 @@ public ITestOutputHelper Output [InlineData(false)] public async Task GrpcTemplate(bool useProgramMain) { - var project = await ProjectFactory.GetOrCreateProject("grpc", Output); + var project = await ProjectFactory.CreateProject(Output); - var args = useProgramMain ? new [] { "--use-program-main" } : null; + var args = useProgramMain ? new [] { ArgConstants.UseProgramMain } : null; var createResult = await project.RunDotNetNewAsync("grpc", args: args); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); diff --git a/src/ProjectTemplates/test/IdentityUIPackageTest.cs b/src/ProjectTemplates/test/IdentityUIPackageTest.cs index aa73178833a8..75b94f804a26 100644 --- a/src/ProjectTemplates/test/IdentityUIPackageTest.cs +++ b/src/ProjectTemplates/test/IdentityUIPackageTest.cs @@ -102,7 +102,7 @@ public ITestOutputHelper Output public async Task IdentityUIPackage_WorksWithDifferentOptions() { var packageOptions = new Dictionary(); - var project = await ProjectFactory.GetOrCreateProject("identityuipackage" + string.Concat(packageOptions.Values), Output); + var project = await ProjectFactory.CreateProject(Output); var useLocalDB = false; var createResult = await project.RunDotNetNewAsync("razor", auth: "Individual", useLocalDB: useLocalDB, environmentVariables: packageOptions); diff --git a/src/ProjectTemplates/test/ItemTemplateTests/BlazorServerTests.cs b/src/ProjectTemplates/test/ItemTemplateTests/BlazorServerTests.cs index 3b97cfdaff78..af904522a756 100644 --- a/src/ProjectTemplates/test/ItemTemplateTests/BlazorServerTests.cs +++ b/src/ProjectTemplates/test/ItemTemplateTests/BlazorServerTests.cs @@ -25,7 +25,7 @@ public BlazorServerTest(ProjectFactoryFixture projectFactory, ITestOutputHelper [Fact] public async Task BlazorServerItemTemplate() { - Project = await ProjectFactory.GetOrCreateProject("razorcomponentitem", Output); + Project = await ProjectFactory.CreateProject(Output); var createResult = await Project.RunDotNetNewAsync("razorcomponent --name Different"); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create", Project, createResult)); diff --git a/src/ProjectTemplates/test/MvcTemplateTest.cs b/src/ProjectTemplates/test/MvcTemplateTest.cs index a5d6bec38066..8984c7bbd7bd 100644 --- a/src/ProjectTemplates/test/MvcTemplateTest.cs +++ b/src/ProjectTemplates/test/MvcTemplateTest.cs @@ -45,11 +45,11 @@ public ITestOutputHelper Output [ConditionalFact] [SkipOnHelix("Cert failure, https://github.com/dotnet/aspnetcore/issues/28090", Queues = "All.OSX;" + HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] - public async Task MvcTemplate_ProgramMainNoAuthCSharp() => await MvcTemplateCore(languageOverride: null, new [] { "--use-program-main" }); + public async Task MvcTemplate_ProgramMainNoAuthCSharp() => await MvcTemplateCore(languageOverride: null, new [] { ArgConstants.UseProgramMain }); private async Task MvcTemplateCore(string languageOverride, string[] args = null) { - var project = await ProjectFactory.GetOrCreateProject("mvcnoauth" + (languageOverride == "F#" ? "fsharp" : "csharp"), Output); + var project = await ProjectFactory.CreateProject(Output); var createResult = await project.RunDotNetNewAsync("mvc", language: languageOverride, args: args); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); @@ -120,16 +120,23 @@ private async Task MvcTemplateCore(string languageOverride, string[] args = null } [ConditionalTheory] - [InlineData(true, false)] - [InlineData(true, true)] - [InlineData(false, false)] - [InlineData(false, true)] + [InlineData(false)] + [InlineData(true)] [SkipOnHelix("Cert failure, https://github.com/dotnet/aspnetcore/issues/28090", Queues = "All.OSX;" + HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] - public async Task MvcTemplate_IndividualAuth(bool useLocalDB, bool useProgramMain) + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX, SkipReason = "No LocalDb on non-Windows")] + public Task MvcTemplate_IndividualAuth_LocalDb(bool useProgramMain) => MvcTemplate_IndividualAuth_Core(useLocalDB: true, useProgramMain); + + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] + [SkipOnHelix("Cert failure, https://github.com/dotnet/aspnetcore/issues/28090", Queues = "All.OSX;" + HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64 + HelixConstants.DebianAmd64)] + public Task MvcTemplate_IndividualAuth(bool useProgramMain) => MvcTemplate_IndividualAuth_Core(useLocalDB: false, useProgramMain); + + private async Task MvcTemplate_IndividualAuth_Core(bool useLocalDB, bool useProgramMain) { - var project = await ProjectFactory.GetOrCreateProject("mvcindividual" + (useLocalDB ? "uld" : ""), Output); + var project = await ProjectFactory.CreateProject(Output); - var args = useProgramMain ? new [] { "--use-program-main" } : null; + var args = useProgramMain ? new [] { ArgConstants.UseProgramMain } : null; var createResult = await project.RunDotNetNewAsync("mvc", auth: "Individual", useLocalDB: useLocalDB, args: args); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); @@ -249,13 +256,13 @@ public async Task MvcTemplate_SingleFileExe() // This test verifies publishing an MVC app as a single file exe works. We'll limit testing // this to a few operating systems to make our lives easier. var runtimeIdentifer = "win-x64"; - var project = await ProjectFactory.GetOrCreateProject("mvcsinglefileexe", Output); + var project = await ProjectFactory.CreateProject(Output); project.RuntimeIdentifier = runtimeIdentifer; var createResult = await project.RunDotNetNewAsync("mvc"); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); - var publishResult = await project.RunDotNetPublishAsync(additionalArgs: $"/p:PublishSingleFile=true -r {runtimeIdentifer}", noRestore: false); + var publishResult = await project.RunDotNetPublishAsync(additionalArgs: $"/p:PublishSingleFile=true -r {runtimeIdentifer} --self-contained", noRestore: false); Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", project, publishResult)); var menuLinks = new[] @@ -292,15 +299,24 @@ public async Task MvcTemplate_SingleFileExe() [ConditionalTheory] [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/28090", Queues = HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] [InlineData("IndividualB2C", null)] - [InlineData("IndividualB2C", new string[] { "--called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })] + [InlineData("IndividualB2C", new[] { ArgConstants.UseProgramMain })] + [InlineData("IndividualB2C", new[] { ArgConstants.CalledApiUrlGraphMicrosoftCom, ArgConstants.CalledApiScopesUserReadWrite })] + [InlineData("IndividualB2C", new[] { ArgConstants.UseProgramMain, ArgConstants.CalledApiUrlGraphMicrosoftCom, ArgConstants.CalledApiScopesUserReadWrite })] + public Task MvcTemplate_IdentityWeb_IndividualB2C_BuildsAndPublishes(string auth, string[] args) => MvcTemplateBuildsAndPublishes(auth: auth, args: args); + + [ConditionalTheory] + [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/28090", Queues = HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] [InlineData("SingleOrg", null)] - [InlineData("SingleOrg", new string[] { "--called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })] - [InlineData("SingleOrg", new string[] { "--calls-graph" })] - public Task MvcTemplate_IdentityWeb_BuildsAndPublishes(string auth, string[] args) => MvcTemplateBuildsAndPublishes(auth: auth, args: args); + [InlineData("SingleOrg", new[] { ArgConstants.UseProgramMain })] + [InlineData("SingleOrg", new[] { ArgConstants.CalledApiUrlGraphMicrosoftCom, ArgConstants.CalledApiScopesUserReadWrite })] + [InlineData("SingleOrg", new[] { ArgConstants.UseProgramMain, ArgConstants.CalledApiUrlGraphMicrosoftCom, ArgConstants.CalledApiScopesUserReadWrite })] + [InlineData("SingleOrg", new[] { ArgConstants.CallsGraph })] + [InlineData("SingleOrg", new[] { ArgConstants.UseProgramMain, ArgConstants.CallsGraph })] + public Task MvcTemplate_IdentityWeb_SingleOrg_BuildsAndPublishes(string auth, string[] args) => MvcTemplateBuildsAndPublishes(auth: auth, args: args); private async Task MvcTemplateBuildsAndPublishes(string auth, string[] args) { - var project = await ProjectFactory.GetOrCreateProject("mvc" + Guid.NewGuid().ToString().Substring(0, 10).ToLowerInvariant(), Output); + var project = await ProjectFactory.CreateProject(Output); var createResult = await project.RunDotNetNewAsync("mvc", auth: auth, args: args); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); diff --git a/src/ProjectTemplates/test/ProjectTemplates.Tests.csproj b/src/ProjectTemplates/test/ProjectTemplates.Tests.csproj index 8a9e1fa661cd..9f3a2986b389 100644 --- a/src/ProjectTemplates/test/ProjectTemplates.Tests.csproj +++ b/src/ProjectTemplates/test/ProjectTemplates.Tests.csproj @@ -19,6 +19,9 @@ TestTemplates\ $([MSBuild]::EnsureTrailingSlash('$(RepoRoot)'))obj\template-restore\ true + + $(HelixQueueArmDebian11); + diff --git a/src/ProjectTemplates/test/RazorClassLibraryTemplateTest.cs b/src/ProjectTemplates/test/RazorClassLibraryTemplateTest.cs index 4a547b2377fe..5c84f9135dec 100644 --- a/src/ProjectTemplates/test/RazorClassLibraryTemplateTest.cs +++ b/src/ProjectTemplates/test/RazorClassLibraryTemplateTest.cs @@ -33,7 +33,7 @@ public ITestOutputHelper Output [Fact] public async Task RazorClassLibraryTemplate_WithViews_Async() { - var project = await ProjectFactory.GetOrCreateProject("razorclasslibwithviews", Output); + var project = await ProjectFactory.CreateProject(Output); var createResult = await project.RunDotNetNewAsync("razorclasslib", args: new[] { "--support-pages-and-views", "true" }); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); @@ -53,7 +53,7 @@ public async Task RazorClassLibraryTemplate_WithViews_Async() [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/28090", Queues = HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] public async Task RazorClassLibraryTemplateAsync() { - var project = await ProjectFactory.GetOrCreateProject("razorclasslib", Output); + var project = await ProjectFactory.CreateProject(Output); var createResult = await project.RunDotNetNewAsync("razorclasslib"); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); diff --git a/src/ProjectTemplates/test/RazorPagesTemplateTest.cs b/src/ProjectTemplates/test/RazorPagesTemplateTest.cs index 3412233f5dd5..ca1affdcadf1 100644 --- a/src/ProjectTemplates/test/RazorPagesTemplateTest.cs +++ b/src/ProjectTemplates/test/RazorPagesTemplateTest.cs @@ -40,9 +40,9 @@ public ITestOutputHelper Output [InlineData(false)] public async Task RazorPagesTemplate_NoAuth(bool useProgramMain) { - var project = await ProjectFactory.GetOrCreateProject("razorpagesnoauth", Output); + var project = await ProjectFactory.CreateProject(Output); - var args = useProgramMain ? new [] { "--use-program-main" } : null; + var args = useProgramMain ? new [] { ArgConstants.UseProgramMain } : null; var createResult = await project.RunDotNetNewAsync("razor", args: args); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("razor", project, createResult)); @@ -107,16 +107,23 @@ public async Task RazorPagesTemplate_NoAuth(bool useProgramMain) } [ConditionalTheory] - [InlineData(false, false)] - [InlineData(false, true)] - [InlineData(true, false)] - [InlineData(true, true)] - [SkipOnHelix("Cert failure, https://github.com/dotnet/aspnetcore/issues/28090", Queues = "All.OSX;" + HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] - public async Task RazorPagesTemplate_IndividualAuth(bool useLocalDB, bool useProgramMain) + [InlineData(false)] + [InlineData(true)] + [SkipOnHelix("Cert failure, https://github.com/dotnet/aspnetcore/issues/28090", Queues = "All.OSX;" + HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64 + HelixConstants.DebianAmd64)] + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX, SkipReason = "No LocalDb on non-Windows")] + public Task RazorPagesTemplate_IndividualAuth_LocalDb(bool useProgramMain) => RazorPagesTemplate_IndividualAuth_Core(useLocalDB: true, useProgramMain); + + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] + [SkipOnHelix("Cert failure, https://github.com/dotnet/aspnetcore/issues/28090", Queues = "All.OSX;" + HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64 + HelixConstants.DebianAmd64)] + public Task RazorPagesTemplate_IndividualAuth(bool useProgramMain) => RazorPagesTemplate_IndividualAuth_Core(useLocalDB: false, useProgramMain); + + private async Task RazorPagesTemplate_IndividualAuth_Core(bool useLocalDB, bool useProgramMain) { - var project = await ProjectFactory.GetOrCreateProject("razorpagesindividual" + (useLocalDB ? "uld" : ""), Output); + var project = await ProjectFactory.CreateProject(Output); - var args = useProgramMain ? new [] { "--use-program-main" } : null; + var args = useProgramMain ? new [] { ArgConstants.UseProgramMain } : null; var createResult = await project.RunDotNetNewAsync("razor", auth: "Individual", useLocalDB: useLocalDB, args: args); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); @@ -231,21 +238,27 @@ public async Task RazorPagesTemplate_IndividualAuth(bool useLocalDB, bool usePro [ConditionalTheory] [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/28090", Queues = HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] [InlineData("IndividualB2C", null)] - [InlineData("IndividualB2C", new string[] { "--called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })] - [InlineData("IndividualB2C", new string[] { "--use-program-main --called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })] + [InlineData("IndividualB2C", new[] { ArgConstants.UseProgramMain })] + [InlineData("IndividualB2C", new[] { ArgConstants.CalledApiUrlGraphMicrosoftCom, ArgConstants.CalledApiScopesUserReadWrite })] + [InlineData("IndividualB2C", new[] { ArgConstants.UseProgramMain, ArgConstants.CalledApiUrlGraphMicrosoftCom, ArgConstants.CalledApiScopesUserReadWrite })] + public Task RazorPagesTemplate_IdentityWeb_IndividualB2C_BuildsAndPublishes(string auth, string[] args) => BuildAndPublishRazorPagesTemplate(auth: auth, args: args); + + [ConditionalTheory] + [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/28090", Queues = HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] [InlineData("SingleOrg", null)] - [InlineData("SingleOrg", new string[] { "--called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })] - [InlineData("SingleOrg", new string[] { "--use-program-main --called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })] - public Task RazorPagesTemplate_IdentityWeb_BuildsAndPublishes(string auth, string[] args) => BuildAndPublishRazorPagesTemplate(auth: auth, args: args); + [InlineData("SingleOrg", new[] { ArgConstants.UseProgramMain })] + [InlineData("SingleOrg", new[] { ArgConstants.CalledApiUrlGraphMicrosoftCom, ArgConstants.CalledApiScopesUserReadWrite })] + [InlineData("SingleOrg", new[] { ArgConstants.UseProgramMain, ArgConstants.CalledApiUrlGraphMicrosoftCom, ArgConstants.CalledApiScopesUserReadWrite })] + public Task RazorPagesTemplate_IdentityWeb_SingleOrg_BuildsAndPublishes(string auth, string[] args) => BuildAndPublishRazorPagesTemplate(auth: auth, args: args); [ConditionalTheory] - [InlineData("SingleOrg", new string[] { "--calls-graph" })] - [InlineData("SingleOrg", new string[] { "--use-program-main --calls-graph" })] - public Task RazorPagesTemplate_IdentityWeb_BuildsAndPublishes_WithSingleOrg(string auth, string[] args) => BuildAndPublishRazorPagesTemplate(auth: auth, args: args); + [InlineData("SingleOrg", new[] { ArgConstants.CallsGraph })] + [InlineData("SingleOrg", new[] { ArgConstants.UseProgramMain, ArgConstants.CallsGraph })] + public Task RazorPagesTemplate_IdentityWeb_SingleOrg_CallsGraph_BuildsAndPublishes(string auth, string[] args) => BuildAndPublishRazorPagesTemplate(auth: auth, args: args); private async Task BuildAndPublishRazorPagesTemplate(string auth, string[] args) { - var project = await ProjectFactory.GetOrCreateProject("razorpages" + Guid.NewGuid().ToString().Substring(0, 10).ToLowerInvariant(), Output); + var project = await ProjectFactory.CreateProject(Output); var createResult = await project.RunDotNetNewAsync("razor", auth: auth, args: args); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); diff --git a/src/ProjectTemplates/test/SpaTemplatesTest.cs b/src/ProjectTemplates/test/SpaTemplatesTest.cs index c5271f7f7ebd..a990217047d5 100644 --- a/src/ProjectTemplates/test/SpaTemplatesTest.cs +++ b/src/ProjectTemplates/test/SpaTemplatesTest.cs @@ -35,13 +35,13 @@ public ITestOutputHelper Output } [Theory] - [InlineData("angularind", "angular", "Individual")] - [InlineData("reactind", "react", "Individual")] - [InlineData("angularnoauth", "angular", null)] - [InlineData("reactnoauth", "react", null)] - public async Task SpaTemplates_BuildAndPublish(string projectKey, string template, string auth) + [InlineData("angular", "Individual")] + [InlineData("react", "Individual")] + [InlineData("angular", null)] + [InlineData("react", null)] + public async Task SpaTemplates_BuildAndPublish(string template, string auth) { - var project = await ProjectFactory.GetOrCreateProject(projectKey, Output); + var project = await ProjectFactory.CreateProject(Output); var args = new[] { "--NoSpaFrontEnd", "true" }; var createResult = await project.RunDotNetNewAsync(template, auth: auth, args: args); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage(template, project, createResult)); diff --git a/src/ProjectTemplates/test/WebApiTemplateTest.cs b/src/ProjectTemplates/test/WebApiTemplateTest.cs index f727abcc92c0..95b483311466 100644 --- a/src/ProjectTemplates/test/WebApiTemplateTest.cs +++ b/src/ProjectTemplates/test/WebApiTemplateTest.cs @@ -35,16 +35,40 @@ public ITestOutputHelper Output [ConditionalTheory] [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/28090", Queues = HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] [InlineData("IndividualB2C", null)] - [InlineData("IndividualB2C", new string[] { "--use-program-main" })] - [InlineData("IndividualB2C", new string[] { "--called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })] - [InlineData("IndividualB2C", new string[] { "--use-program-main --called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })] + [InlineData("IndividualB2C", new string[] { ArgConstants.UseMinimalApis })] + [InlineData("IndividualB2C", new string[] { ArgConstants.CalledApiUrlGraphMicrosoftCom, ArgConstants.CalledApiScopesUserReadWrite })] + [InlineData("IndividualB2C", new string[] { ArgConstants.UseMinimalApis, ArgConstants.CalledApiUrlGraphMicrosoftCom, ArgConstants.CalledApiScopesUserReadWrite })] + public Task WebApiTemplateCSharp_IdentityWeb_IndividualB2C_BuildsAndPublishes(string auth, string[] args) => PublishAndBuildWebApiTemplate(languageOverride: null, auth: auth, args: args); + + [ConditionalTheory] + [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/28090", Queues = HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] + [InlineData("IndividualB2C", null)] + [InlineData("IndividualB2C", new string[] { ArgConstants.UseProgramMain })] + [InlineData("IndividualB2C", new string[] { ArgConstants.UseProgramMain, ArgConstants.UseMinimalApis })] + [InlineData("IndividualB2C", new string[] { ArgConstants.UseProgramMain, ArgConstants.CalledApiUrlGraphMicrosoftCom, ArgConstants.CalledApiScopesUserReadWrite })] + [InlineData("IndividualB2C", new string[] { ArgConstants.UseProgramMain, ArgConstants.UseMinimalApis, ArgConstants.CalledApiUrlGraphMicrosoftCom, ArgConstants.CalledApiScopesUserReadWrite })] + public Task WebApiTemplateCSharp_IdentityWeb_IndividualB2C_ProgramMain_BuildsAndPublishes(string auth, string[] args) => PublishAndBuildWebApiTemplate(languageOverride: null, auth: auth, args: args); + + [ConditionalTheory] + [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/28090", Queues = HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] + [InlineData("SingleOrg", null)] + [InlineData("SingleOrg", new string[] { ArgConstants.UseMinimalApis })] + [InlineData("SingleOrg", new string[] { ArgConstants.CalledApiUrlGraphMicrosoftCom, ArgConstants.CalledApiScopesUserReadWrite })] + [InlineData("SingleOrg", new string[] { ArgConstants.UseMinimalApis, ArgConstants.CalledApiUrlGraphMicrosoftCom, ArgConstants.CalledApiScopesUserReadWrite })] + [InlineData("SingleOrg", new string[] { ArgConstants.CallsGraph })] + [InlineData("SingleOrg", new string[] { ArgConstants.UseMinimalApis, ArgConstants.CallsGraph })] + public Task WebApiTemplateCSharp_IdentityWeb_SingleOrg_BuildsAndPublishes(string auth, string[] args) => PublishAndBuildWebApiTemplate(languageOverride: null, auth: auth, args: args); + + [ConditionalTheory] + [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/28090", Queues = HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] [InlineData("SingleOrg", null)] - [InlineData("SingleOrg", new string[] { "--use-program-main" })] - [InlineData("SingleOrg", new string[] { "--called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })] - [InlineData("SingleOrg", new string[] { "--use-program-main --called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })] - [InlineData("SingleOrg", new string[] { "--calls-graph" })] - [InlineData("SingleOrg", new string[] { "--use-program-main --calls-graph" })] - public Task WebApiTemplateCSharp_IdentityWeb_BuildsAndPublishes(string auth, string[] args) => PublishAndBuildWebApiTemplate(languageOverride: null, auth: auth, args: args); + [InlineData("SingleOrg", new string[] { ArgConstants.UseProgramMain })] + [InlineData("SingleOrg", new string[] { ArgConstants.UseProgramMain, ArgConstants.UseMinimalApis })] + [InlineData("SingleOrg", new string[] { ArgConstants.UseProgramMain, ArgConstants.CalledApiUrlGraphMicrosoftCom, ArgConstants.CalledApiScopesUserReadWrite })] + [InlineData("SingleOrg", new string[] { ArgConstants.UseProgramMain, ArgConstants.UseMinimalApis, ArgConstants.CalledApiUrlGraphMicrosoftCom, ArgConstants.CalledApiScopesUserReadWrite })] + [InlineData("SingleOrg", new string[] { ArgConstants.UseProgramMain, ArgConstants.CallsGraph })] + [InlineData("SingleOrg", new string[] { ArgConstants.UseProgramMain, ArgConstants.UseMinimalApis, ArgConstants.CallsGraph })] + public Task WebApiTemplateCSharp_IdentityWeb_SingleOrg_ProgramMain_BuildsAndPublishes(string auth, string[] args) => PublishAndBuildWebApiTemplate(languageOverride: null, auth: auth, args: args); [Fact] public Task WebApiTemplateFSharp() => WebApiTemplateCore(languageOverride: "F#"); @@ -55,17 +79,33 @@ public ITestOutputHelper Output [ConditionalFact] [SkipOnHelix("Cert failure, https://github.com/dotnet/aspnetcore/issues/28090", Queues = "All.OSX;" + HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] - public Task WebApiTemplateProgramMainCSharp() => WebApiTemplateCore(languageOverride: null, args: new [] { "--use-program-main" }); + public Task WebApiTemplateProgramMainCSharp() => WebApiTemplateCore(languageOverride: null, args: new [] { ArgConstants.UseProgramMain }); + + [ConditionalFact] + [SkipOnHelix("Cert failure, https://github.com/dotnet/aspnetcore/issues/28090", Queues = "All.OSX;" + HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] + public Task WebApiTemplateMinimalApisCSharp() => WebApiTemplateCore(languageOverride: null, args: new[] { ArgConstants.UseMinimalApis }); + + [ConditionalFact] + [SkipOnHelix("Cert failure, https://github.com/dotnet/aspnetcore/issues/28090", Queues = "All.OSX;" + HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] + public Task WebApiTemplateProgramMainMinimalApisCSharp() => WebApiTemplateCore(languageOverride: null, args: new[] { ArgConstants.UseProgramMain, ArgConstants.UseMinimalApis }); [ConditionalTheory] - [InlineData(false)] - [InlineData(true)] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, true)] + [InlineData(true, false)] [SkipOnHelix("Cert failure, https://github.com/dotnet/aspnetcore/issues/28090", Queues = "All.OSX;" + HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] - public async Task WebApiTemplateCSharp_WithoutOpenAPI(bool useProgramMain) + public async Task WebApiTemplateCSharp_WithoutOpenAPI(bool useProgramMain, bool useMinimalApis) { - var project = await FactoryFixture.GetOrCreateProject("webapinoopenapi", Output); - - var args = useProgramMain ? new[] { "--use-program-main --no-openapi" } : new[] { "--no-openapi" }; + var project = await FactoryFixture.CreateProject(Output); + + var args = useProgramMain + ? useMinimalApis + ? new[] { ArgConstants.UseProgramMain, ArgConstants.UseMinimalApis, ArgConstants.NoOpenApi } + : new[] { ArgConstants.UseProgramMain, ArgConstants.NoOpenApi } + : useMinimalApis + ? new[] { ArgConstants.UseMinimalApis, ArgConstants.NoOpenApi } + : new[] { ArgConstants.NoOpenApi }; var createResult = await project.RunDotNetNewAsync("webapi", args: args); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); diff --git a/src/ProjectTemplates/test/WorkerTemplateTest.cs b/src/ProjectTemplates/test/WorkerTemplateTest.cs index ba82b5073c0e..60b3f519040b 100644 --- a/src/ProjectTemplates/test/WorkerTemplateTest.cs +++ b/src/ProjectTemplates/test/WorkerTemplateTest.cs @@ -33,14 +33,12 @@ public ITestOutputHelper Output [ConditionalTheory] [OSSkipCondition(OperatingSystems.Linux, SkipReason = "https://github.com/dotnet/sdk/issues/12831")] [InlineData("C#", null)] - [InlineData("C#", new string[] { "--use-program-main" })] + [InlineData("C#", new string[] { ArgConstants.UseProgramMain })] [InlineData("F#", null)] [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/25404")] public async Task WorkerTemplateAsync(string language, string[] args) { - var project = await ProjectFactory.GetOrCreateProject( - $"worker-{ language.ToLowerInvariant()[0] }sharp", - Output); + var project = await ProjectFactory.CreateProject(Output); var createResult = await project.RunDotNetNewAsync("worker", language: language, args: args); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); diff --git a/src/ProjectTemplates/test/template-baselines.json b/src/ProjectTemplates/test/template-baselines.json index aa35c00adfe4..4c364f3b9298 100644 --- a/src/ProjectTemplates/test/template-baselines.json +++ b/src/ProjectTemplates/test/template-baselines.json @@ -541,6 +541,19 @@ ], "AuthOption": "IndividualB2C" }, + "IndividualB2CProgramMain": { + "Template": "webapi", + "Arguments": "new webapi -au IndividualB2C --use-program-main --aad-b2c-instance https://login.microsoftonline.com/tfp/ --domain fake-b2c-domain.onmicrosoft.com --client-id 64f31f76-2750-49e4-aab9-f5de105b5172 -ssp B2C_1_SiUpIn", + "Files": [ + "appsettings.Development.json", + "appsettings.json", + "Program.cs", + "WeatherForecast.cs", + "Controllers/WeatherForecastController.cs", + "Properties/launchSettings.json" + ], + "AuthOption": "IndividualB2C" + }, "SingleOrg": { "Template": "webapi", "Arguments": "new webapi -au SingleOrg --aad-instance https://login.microsoftonline.com/ --domain fake-aad-domain.onmicrosoft.com --client-id db33c356-12cc-4953-9167-00ad56c2e8b2 --tenant-id 7e511586-66ec-4108-bc9c-a68dee0dc2aa", @@ -554,6 +567,19 @@ ], "AuthOption": "SingleOrg" }, + "SingleOrgProgramMain": { + "Template": "webapi", + "Arguments": "new webapi -au SingleOrg --use-program-main --aad-instance https://login.microsoftonline.com/ --domain fake-aad-domain.onmicrosoft.com --client-id db33c356-12cc-4953-9167-00ad56c2e8b2 --tenant-id 7e511586-66ec-4108-bc9c-a68dee0dc2aa", + "Files": [ + "appsettings.Development.json", + "appsettings.json", + "Program.cs", + "WeatherForecast.cs", + "Controllers/WeatherForecastController.cs", + "Properties/launchSettings.json" + ], + "AuthOption": "SingleOrg" + }, "None": { "Template": "webapi", "Arguments": "new webapi -au None", @@ -567,6 +593,19 @@ ], "AuthOption": "None" }, + "NoneProgramMain": { + "Template": "webapi", + "Arguments": "new webapi -au None --use-program-main", + "Files": [ + "appsettings.Development.json", + "appsettings.json", + "Program.cs", + "WeatherForecast.cs", + "Controllers/WeatherForecastController.cs", + "Properties/launchSettings.json" + ], + "AuthOption": "None" + }, "MinimalIndividualB2C": { "Template": "webapi", "Arguments": "new webapi -minimal -au IndividualB2C --aad-b2c-instance https://login.microsoftonline.com/tfp/ --domain fake-b2c-domain.onmicrosoft.com --client-id 64f31f76-2750-49e4-aab9-f5de105b5172 -ssp B2C_1_SiUpIn", @@ -578,6 +617,18 @@ ], "AuthOption": "IndividualB2C" }, + "MinimalIndividualB2CProgramMain": { + "Template": "webapi", + "Arguments": "new webapi -minimal --use-program-main -au IndividualB2C --aad-b2c-instance https://login.microsoftonline.com/tfp/ --domain fake-b2c-domain.onmicrosoft.com --client-id 64f31f76-2750-49e4-aab9-f5de105b5172 -ssp B2C_1_SiUpIn", + "Files": [ + "appsettings.Development.json", + "appsettings.json", + "Program.cs", + "WeatherForecast.cs", + "Properties/launchSettings.json" + ], + "AuthOption": "IndividualB2C" + }, "MinimalSingleOrg": { "Template": "webapi", "Arguments": "new webapi -minimal -au SingleOrg --aad-instance https://login.microsoftonline.com/ --domain fake-aad-domain.onmicrosoft.com --client-id db33c356-12cc-4953-9167-00ad56c2e8b2 --tenant-id 7e511586-66ec-4108-bc9c-a68dee0dc2aa", @@ -589,6 +640,18 @@ ], "AuthOption": "SingleOrg" }, + "MinimalSingleOrgProgramMain": { + "Template": "webapi", + "Arguments": "new webapi -minimal --use-program-main -au SingleOrg --aad-instance https://login.microsoftonline.com/ --domain fake-aad-domain.onmicrosoft.com --client-id db33c356-12cc-4953-9167-00ad56c2e8b2 --tenant-id 7e511586-66ec-4108-bc9c-a68dee0dc2aa", + "Files": [ + "appsettings.Development.json", + "appsettings.json", + "Program.cs", + "WeatherForecast.cs", + "Properties/launchSettings.json" + ], + "AuthOption": "SingleOrg" + }, "Minimal": { "Template": "webapi", "Arguments": "new webapi -minimal -au None", @@ -600,6 +663,18 @@ ], "AuthOption": "None" }, + "MinimalProgramMain": { + "Template": "webapi", + "Arguments": "new webapi -minimal --use-program-main -au None", + "Files": [ + "appsettings.Development.json", + "appsettings.json", + "Program.cs", + "WeatherForecast.cs", + "Properties/launchSettings.json" + ], + "AuthOption": "None" + }, "Windows": { "Template": "webapi", "Arguments": "new webapi -au Windows", @@ -613,6 +688,19 @@ ], "AuthOption": "Windows" }, + "WindowsProgramMain": { + "Template": "webapi", + "Arguments": "new webapi -au Windows --use-program-main", + "Files": [ + "appsettings.Development.json", + "appsettings.json", + "Program.cs", + "WeatherForecast.cs", + "Controllers/WeatherForecastController.cs", + "Properties/launchSettings.json" + ], + "AuthOption": "Windows" + }, "MinimalWindows": { "Template": "webapi", "Arguments": "new webapi -minimal -au Windows", @@ -624,6 +712,18 @@ ], "AuthOption": "Windows" }, + "MinimalWindowsProgramMain": { + "Template": "webapi", + "Arguments": "new webapi -minimal --use-program-main -au Windows", + "Files": [ + "appsettings.Development.json", + "appsettings.json", + "Program.cs", + "WeatherForecast.cs", + "Properties/launchSettings.json" + ], + "AuthOption": "Windows" + }, "FSharp": { "Template": "webapi", "Arguments": "new webapi --language F#", diff --git a/src/Shared/CertificateGeneration/CertificateManager.cs b/src/Shared/CertificateGeneration/CertificateManager.cs index 99f8e446a7d3..987db2bba031 100644 --- a/src/Shared/CertificateGeneration/CertificateManager.cs +++ b/src/Shared/CertificateGeneration/CertificateManager.cs @@ -493,11 +493,11 @@ internal void ExportCertificate(X509Certificate2 certificate, string path, bool // Export the key first to an encrypted PEM to avoid issues with System.Security.Cryptography.Cng indicating that the operation is not supported. // This is likely by design to avoid exporting the key by mistake. // To bypass it, we export the certificate to pem temporarily and then we import it and export it as unprotected PEM. - keyBytes = key.ExportEncryptedPkcs8PrivateKey("", new PbeParameters(PbeEncryptionAlgorithm.Aes256Cbc, HashAlgorithmName.SHA256, 1)); + keyBytes = key.ExportEncryptedPkcs8PrivateKey((ReadOnlySpan)"", new PbeParameters(PbeEncryptionAlgorithm.Aes256Cbc, HashAlgorithmName.SHA256, 1)); pem = PemEncoding.Write("ENCRYPTED PRIVATE KEY", keyBytes); key.Dispose(); key = RSA.Create(); - key.ImportFromEncryptedPem(pem, ""); + key.ImportFromEncryptedPem(pem, (ReadOnlySpan)""); Array.Clear(keyBytes, 0, keyBytes.Length); Array.Clear(pem, 0, pem.Length); keyBytes = key.ExportPkcs8PrivateKey(); diff --git a/src/Testing/src/xunit/HelixConstants.cs b/src/Testing/src/xunit/HelixConstants.cs index 8ec6a086a7a0..a78e9e2b411b 100644 --- a/src/Testing/src/xunit/HelixConstants.cs +++ b/src/Testing/src/xunit/HelixConstants.cs @@ -6,6 +6,7 @@ namespace Microsoft.AspNetCore.Testing public static class HelixConstants { public const string Windows10Arm64 = "Windows.10.Arm64v8.Open;"; + public const string DebianAmd64 = "Debian.11.Amd64.Open;"; public const string DebianArm64 = "Debian.11.Arm64.Open;"; public const string RedhatAmd64 = "Redhat.7.Amd64.Open;"; }