| File: TestDevTunnelMonitor.cs | Web Access |
| Project: src\tests\Aspire.Hosting.DevTunnels.Tests\Aspire.Hosting.DevTunnels.Tests.csproj (Aspire.Hosting.DevTunnels.Tests) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.Testing; using Aspire.Hosting.Tests; using Aspire.Hosting.Utils; using Microsoft.AspNetCore.InternalTesting; using Microsoft.Extensions.DependencyInjection; namespace Aspire.Hosting.DevTunnels.Tests; internal sealed class TestDevTunnelMonitor : IDisposable { private readonly IDistributedApplicationTestingBuilder _builder = TestDistributedApplicationBuilder.Create(); public TestDevTunnelMonitor(TimeProvider? timeProvider = null, bool includeSecondPort = false) { if (timeProvider is not null) { _builder.Services.AddSingleton(timeProvider); } _builder.Services.AddSingleton<IDevTunnelClient>(Client); _builder.Services.AddSingleton<IInteractionService>(Interaction); var target = _builder.AddExecutable("target", "unused", _builder.AppHostDirectory) .WithHttpEndpoint(targetPort: 3000); var targetEndpoint = target.GetEndpoint("http"); targetEndpoint.EndpointAnnotation.AllocatedEndpoint = new(targetEndpoint.EndpointAnnotation, "localhost", 3000); if (includeSecondPort) { target.WithHttpEndpoint(targetPort: 3001, name: "other"); var otherEndpoint = target.GetEndpoint("other"); otherEndpoint.EndpointAnnotation.AllocatedEndpoint = new(otherEndpoint.EndpointAnnotation, "localhost", 3001); } Tunnel = _builder.AddDevTunnel("tunnel", "mytunnel").WithReference(target).Resource; Port = Assert.Single(Tunnel.Ports, p => p.TargetEndpoint.EndpointName == "http"); App = _builder.Build(); Monitor = App.Services.GetRequiredKeyedService<DevTunnelMonitor>(Tunnel); // Tests opt into fallback explicitly; host load must not select that path accidentally. Monitor.StartupLogTimeout = TimeSpan.FromMinutes(1); } public TestDevTunnelClient Client { get; } = new() { TunnelStatus = new("mytunnel.usw2", 1, 0, "", []) { Ports = [new(3000, "http") { PortUri = new("https://original-3000.usw2.devtunnels.ms/") }] } }; public DistributedApplication App { get; } public TestInteractionService Interaction { get; } = new() { IsAvailable = false }; public DevTunnelMonitor Monitor { get; } public DevTunnelResource Tunnel { get; } public DevTunnelPortResource Port { get; } public async Task StartAsync() { await Monitor.StartAsync("mytunnel.usw2", CancellationToken.None).DefaultTimeout(); await App.ResourceNotifications.PublishUpdateAsync(Tunnel, s => s with { State = KnownResourceStates.Running }); } public Task LogAsync(string content) => Monitor.ProcessLogAsync(content, CancellationToken.None).DefaultTimeout(); public async Task ReconcileAsync() { await Monitor.CheckHealthAsync(CancellationToken.None).DefaultTimeout(); await Monitor.WaitForAccessRefreshAsync(CancellationToken.None).DefaultTimeout(); } public async Task ReadyAsync() { await LogAsync(""" Connection to host tunnel relay restored. Hosting port: 3000 Connect via browser: https://original-3000.usw2.devtunnels.ms Inspect network activity: https://original-3000-inspect.usw2.devtunnels.ms Ready to accept connections for tunnel: mytunnel.usw2 """); if (Tunnel.Ports.Count > 1) { await LogAsync(""" Hosting port: 3001 Connect via browser: https://original-3001.usw2.devtunnels.ms """); } await App.ResourceNotifications.WaitForResourceAsync(Port.Name, e => e.Snapshot.State?.Text == KnownResourceStates.Running && e.Snapshot.HealthStatus == Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus.Healthy).DefaultTimeout(); } public CustomResourceSnapshot Snapshot(IResource resource) { Assert.True(App.ResourceNotifications.TryGetCurrentState(resource.Name, out var current)); return current.Snapshot; } public async Task<IReadOnlyList<LogLine>> LogsAsync(IResource? resource = null) { await using var reader = App.Services.GetRequiredService<ResourceLoggerService>().WatchAsync(resource ?? Tunnel).GetAsyncEnumerator(); Assert.True(await reader.MoveNextAsync().AsTask().DefaultTimeout()); return reader.Current; } public void Dispose() { App.Dispose(); _builder.Dispose(); } }