| File: TestServices\FakeAppHostServerSession.cs | Web Access |
| Project: src\tests\Aspire.Cli.Tests\Aspire.Cli.Tests.csproj (Aspire.Cli.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.Text.Json; using Aspire.Cli.Commands.Sdk; using Aspire.Cli.Processes; using Aspire.Cli.Projects; using Aspire.Cli.Utils; using Aspire.TypeSystem; namespace Aspire.Cli.Tests.TestServices; /// <summary> /// Fake <see cref="IAppHostServerSession"/> that returns a <see cref="FakeAppHostRpcClient"/> /// without starting a real process or connecting to a socket. /// </summary> internal sealed class FakeAppHostServerSession : IAppHostServerSession { private readonly IAppHostRpcClient _rpcClient; private readonly TaskCompletionSource<int> _exit = new(); public FakeAppHostServerSession(IAppHostRpcClient? rpcClient = null) { _rpcClient = rpcClient ?? new FakeAppHostRpcClient(); } public Func<Task>? StartAsyncCallback { get; init; } public Func<CancellationToken, Task<IAppHostRpcClient>>? GetRpcClientAsyncCallback { get; init; } public bool StartAsyncCalled { get; private set; } public string AuthenticationToken { get; } = "fake-token"; public string? SocketPath { get; } = "fake.sock"; public OutputCollector? Output { get; } = new(); public bool? ServerHasExited { get; set; } public int? ServerExitCode { get; set; } public bool? HasServerExited => ServerHasExited ?? _exit.Task.IsCompleted; public int? TryGetServerExitCode() { if (ServerHasExited == true) { return ServerExitCode; } return _exit.Task.IsCompletedSuccessfully ? _exit.Task.Result : null; } public async Task StartAsync() { StartAsyncCalled = true; if (StartAsyncCallback is not null) { await StartAsyncCallback(); } } public Task<int> WaitForExitAsync() => _exit.Task; public Task<IAppHostRpcClient> GetRpcClientAsync(CancellationToken cancellationToken) => GetRpcClientAsyncCallback is not null ? GetRpcClientAsyncCallback(cancellationToken) : Task.FromResult(_rpcClient); public ValueTask DisposeAsync() { _exit.TrySetResult(0); return ValueTask.CompletedTask; } } /// <summary> /// Fake <see cref="IAppHostServerSessionFactory"/> that hands back a <see cref="FakeAppHostServerSession"/> /// without building or launching a real AppHost server. /// </summary> internal sealed class FakeAppHostServerSessionFactory : IAppHostServerSessionFactory { public IAppHostServerSession? Session { get; init; } public Dictionary<string, string>? CapturedEnvironmentVariables { get; private set; } public static FakeAppHostServerSessionFactory CreateForScaffolding( IReadOnlyDictionary<string, string>? scaffoldFiles = null) { var rpcClient = new FakeAppHostRpcClient { ScaffoldAppHostAsyncCallback = (_, _, _, _) => Task.FromResult(scaffoldFiles is null ? new Dictionary<string, string>() : new Dictionary<string, string>(scaffoldFiles)) }; return new FakeAppHostServerSessionFactory { Session = new FakeAppHostServerSession(rpcClient) }; } public IAppHostServerSession Create( IAppHostServerProject appHostServerProject, Dictionary<string, string>? environmentVariables, bool debug, IProcessTreeGracefulShutdownSignaler? gracefulShutdownSignaler, IGracefulShutdownWindow? shutdownService, bool isolateConsole, CancellationToken stopRequested) { CapturedEnvironmentVariables = environmentVariables is null ? null : new Dictionary<string, string>(environmentVariables); return Session ?? new FakeAppHostServerSession(); } } /// <summary> /// Fake RPC client that returns empty results for all operations. /// Used to exercise code paths that run after RPC connection without needing a real server. /// Members are virtual so a test can override just the call it exercises. /// </summary> internal class FakeAppHostRpcClient : IAppHostRpcClient { public RuntimeSpec? RuntimeSpec { get; init; } public Func<string, string, string?, CancellationToken, Task<Dictionary<string, string>>>? ScaffoldAppHostAsyncCallback { get; init; } public virtual Task<RuntimeSpec> GetRuntimeSpecAsync(string languageId, CancellationToken cancellationToken) => Task.FromResult(RuntimeSpec ?? new RuntimeSpec { Language = languageId, DisplayName = "Fake", CodeGenLanguage = "TypeScript", DetectionPatterns = ["apphost.ts"], Execute = new CommandSpec { Command = "node", Args = ["apphost.js"] } }); public virtual Task<Dictionary<string, string>> ScaffoldAppHostAsync(string languageId, string targetPath, string? projectName, CancellationToken cancellationToken) => ScaffoldAppHostAsyncCallback is not null ? ScaffoldAppHostAsyncCallback(languageId, targetPath, projectName, cancellationToken) : throw new NotSupportedException(); public virtual Task<Dictionary<string, string>> GenerateCodeAsync(string languageId, CancellationToken cancellationToken) => Task.FromResult(new Dictionary<string, string>()); public virtual Task<Dictionary<string, string>> GenerateCodeForAssemblyAsync(string languageId, string assemblyName, CancellationToken cancellationToken) => Task.FromResult(new Dictionary<string, string>()); public virtual Task<CapabilitiesInfo> GetCapabilitiesAsync(CancellationToken cancellationToken) => throw new NotSupportedException(); public virtual Task<CapabilitiesInfo> GetCapabilitiesForAssembliesAsync(IReadOnlyList<string> assemblyNames, CancellationToken cancellationToken) => throw new NotSupportedException(); public virtual Task<JsonElement> ExportApiAsync(string languageId, string packageName, string packageVersion, CancellationToken cancellationToken) => throw new NotSupportedException(); public virtual Task<T> InvokeAsync<T>(string methodName, object?[] parameters, CancellationToken cancellationToken) => throw new NotSupportedException(); public virtual Task InvokeAsync(string methodName, object?[] parameters, CancellationToken cancellationToken) => throw new NotSupportedException(); public ValueTask DisposeAsync() => ValueTask.CompletedTask; }