Repository: dotnet/aspnetcore
File: src/ProjectTemplates/test/Templates.Tests/EmptyWebTemplateTest.cs (C#)

Code before:
    [SkipOnHelix("Cert failure, https://github.com/dotnet/aspnetcore/issues/28090", Queues = "All.OSX;" + HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)]
    public async Task EmptyWebTemplateLocalhostTldWithTrailingDot()
    {
        var project = await ProjectFactory.CreateProject(Output, "EndWithDot.");

        await project.RunDotNetNewAsync("web", args: new[] { ArgConstants.LocalhostTld });

        var expectedLaunchProfileNames = new[] { "http", "https" };
        await project.VerifyLaunchSettings(expectedLaunchProfileNames);
        await VerifyDnsCompliantHostname(project, "endwithdot");
    }

    [ConditionalFact]
    [SkipOnHelix("Cert failure, https://github.com/dotnet/aspnetcore/issues/28090", Queues = "All.OSX;" + HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)]
    public async Task EmptyWebTemplateLocalhostTldWithMultipleInvalidChars()
    {
        var project = await ProjectFactory.CreateProject(Output, "My..Test__Project");

        await project.RunDotNetNewAsync("web", args: new[] { ArgConstants.LocalhostTld });

        var expectedLaunchProfileNames = new[] { "http", "https" };
        await project.VerifyLaunchSettings(expectedLaunchProfileNames);
        await VerifyDnsCompliantHostname(project, "my--test--project");
    }

    private async Task VerifyDnsCompliantHostname(Project project, string expectedHostname)
    {
        var launchSettingsPath = Path.Combine(project.TemplateOutputDir, "Properties", "launchSettings.json");
        Assert.True(File.Exists(launchSettingsPath), $"launchSettings.json not found at {launchSettingsPath}");

        var launchSettingsContent = await File.ReadAllTextAsync(launchSettingsPath);
        using var launchSettings = JsonDocument.Parse(launchSettingsContent);

        var profiles = launchSettings.RootElement.GetProperty("profiles");

        foreach (var profile in profiles.EnumerateObject())
        {
            if (profile.Value.TryGetProperty("applicationUrl", out var applicationUrl))
            {
                var urls = applicationUrl.GetString();
                if (!string.IsNullOrEmpty(urls))
                {
                    // Verify the hostname in the URL matches expected DNS-compliant format
                    Assert.Contains($"{expectedHostname}.dev.localhost:", urls);
                    
                    // Verify no underscores in hostname (RFC 952/1123 compliance)
                    var hostnamePattern = @"://([^:]+)\.dev\.localhost:";
                    var matches = System.Text.RegularExpressions.Regex.Matches(urls, hostnamePattern);
                    foreach (System.Text.RegularExpressions.Match match in matches)
                    {
                        var hostname = match.Groups[1].Value;

Review comment (refactor):
@copilot This method is duplicated between the two test projects. Move it to the code shared by template tests.

Code after:
            : new[] { "http", "https" };
        await project.VerifyLaunchSettings(expectedLaunchProfileNames);

        // Avoid the F# compiler. See https://github.com/dotnet/aspnetcore/issues/14022
        if (languageOverride != null)
        {
            return;
        }

        await project.RunDotNetPublishAsync();

        // Run dotnet build after publish. The reason is that one uses Config = Debug and the other uses Config = Release
        // The output from publish will go into bin/Release/netcoreappX.Y/publish and won't be affected by calling build
        // later, while the opposite is not true.

        await project.RunDotNetBuildAsync();

        using (var aspNetProcess = project.StartBuiltProjectAsync())
        {
            Assert.False(
               aspNetProcess.Process.HasExited,
               ErrorMessages.GetFailedProcessMessageOrEmpty("Run built project", project, aspNetProcess.Process));

            await aspNetProcess.AssertOk("/");
        }

        using (var aspNetProcess = project.StartPublishedProjectAsync())
        {
            Assert.False(
                aspNetProcess.Process.HasExited,
                ErrorMessages.GetFailedProcessMessageOrEmpty("Run published project", project, aspNetProcess.Process));

            await aspNetProcess.AssertOk("/");
        }
    }
}