| File: Helpers\DotnetProjectDeploymentHelpers.cs | Web Access |
| Project: src\tests\Aspire.Deployment.EndToEnd.Tests\Aspire.Deployment.EndToEnd.Tests.csproj (Aspire.Deployment.EndToEnd.Tests) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.ComponentModel; using System.Diagnostics; using System.Net; using System.Net.Http.Headers; using Hex1b.Automation; using Xunit; namespace Aspire.Deployment.EndToEnd.Tests.Helpers; internal static class DotnetProjectDeploymentHelpers { internal static string GetSubscriptionId() { var subscriptionId = AzureAuthenticationHelpers.TryGetSubscriptionId(); if (string.IsNullOrEmpty(subscriptionId)) { Assert.Skip("Azure subscription not configured. Set ASPIRE_DEPLOYMENT_TEST_SUBSCRIPTION."); } if (!AzureAuthenticationHelpers.IsAzureAuthAvailable()) { if (DeploymentE2ETestHelpers.IsRunningInCI) { Assert.Fail("Azure authentication not available in CI. Check OIDC configuration."); } Assert.Skip("Azure authentication not available. Run 'az login' to authenticate."); } return subscriptionId; } internal static async Task AddPackageAsync(Hex1bTerminalAutomator auto, SequenceCounter counter, string packageName) { // aspire add resolves through the installed CLI's channel/local hive, not nuget.org's latest release. await auto.TypeAsync($"aspire add {packageName}"); await auto.EnterAsync(); await auto.WaitForAspireAddCompletionAsync(counter, TimeSpan.FromMinutes(3)); } internal static string ReplaceExactlyOnce(string content, string oldValue, string newValue) { var index = content.IndexOf(oldValue, StringComparison.Ordinal); if (index < 0 || content.IndexOf(oldValue, index + oldValue.Length, StringComparison.Ordinal) >= 0) { throw new InvalidOperationException($"Expected exactly one '{oldValue}' in the generated source."); } return content.Replace(oldValue, newValue, StringComparison.Ordinal); } internal static void ReplaceInFile(string path, string oldValue, string newValue) { File.WriteAllText(path, ReplaceExactlyOnce(File.ReadAllText(path), oldValue, newValue)); } internal static Task RunScriptAsync(Hex1bTerminalAutomator auto, SequenceCounter counter, string script, TimeSpan timeout) { return auto.RunCommandAsync($"bash -c {AspireCliShellCommandHelpers.QuoteBashArg(script)}", counter, timeout); } internal static async Task VerifyResponseAsync( string url, string expected, CancellationToken cancellationToken, AuthenticationHeaderValue? authorization = null) { using var handler = new HttpClientHandler { AllowAutoRedirect = false }; using var client = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(15) }; string? lastResult = null; for (var attempt = 0; attempt < 18; attempt++) { try { using var request = new HttpRequestMessage(HttpMethod.Get, url); request.Headers.Authorization = authorization; using var response = await client.SendAsync(request, cancellationToken); var body = await response.Content.ReadAsStringAsync(cancellationToken); if (response.StatusCode == HttpStatusCode.OK && body == expected) { return; } lastResult = $"HTTP {(int)response.StatusCode}, body: {body}"; } catch (Exception ex) when (ex is HttpRequestException || ex is TaskCanceledException && !cancellationToken.IsCancellationRequested) { lastResult = ex.Message; } await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken); } Assert.Fail($"The deployed workload at {url} did not return '{expected}'. Last result: {lastResult}"); } internal static async Task CleanupAsync(string resourceGroupName, string subscriptionId, ITestOutputHelper output) { using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(2)); try { using var process = new Process { StartInfo = new ProcessStartInfo("az") { RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false } }; foreach (var argument in new[] { "group", "delete", "--name", resourceGroupName, "--subscription", subscriptionId, "--yes", "--no-wait" }) { process.StartInfo.ArgumentList.Add(argument); } process.Start(); try { var stdout = process.StandardOutput.ReadToEndAsync(cts.Token); var stderr = process.StandardError.ReadToEndAsync(cts.Token); await process.WaitForExitAsync(cts.Token); await stdout; var error = await stderr; DeploymentReporter.ReportCleanupStatus(resourceGroupName, process.ExitCode == 0, process.ExitCode == 0 ? "Resource group deletion requested." : error); } finally { if (!process.HasExited) { process.Kill(entireProcessTree: true); } } } catch (Exception ex) when (ex is Win32Exception or IOException or InvalidOperationException or OperationCanceledException) { output.WriteLine($"Cleanup failed for {resourceGroupName}: {ex.Message}"); DeploymentReporter.ReportCleanupStatus(resourceGroupName, false, ex.Message); } } }