using MeetingAssistant; using MeetingAssistant.LaunchProfiles; using MeetingAssistant.Transcription; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; namespace MeetingAssistant.Tests; public sealed class PyannoteDiarizationWarmupHostedServiceTests { [Fact] public async Task HostedServiceWarmsEnabledValidationRuntimeWithoutBlockingStartup() { var commandRunner = new BlockingCommandRunner(); var finalizer = new PyannoteTranscriptFinalizer( commandRunner, Options.Create(new MeetingAssistantOptions()), NullLogger.Instance); var service = new PyannoteDiarizationWarmupHostedService( finalizer, new FakeLaunchProfileOptionsProvider(new MeetingAssistantOptions { Recording = { TranscriptionProvider = "azure-speech" }, SpeakerIdentification = { PyannoteValidation = { Enabled = true, Diarization = { Enabled = true, DockerCommand = "docker", Image = "meeting-assistant-pyannote-validation:local", ModelsFolder = Path.Combine( Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N"), "models"), Token = "hf_test", TokenEnv = "", CommandTimeout = TimeSpan.FromMinutes(1) } } } }), NullLogger.Instance); await service.StartAsync(CancellationToken.None).WaitAsync(TimeSpan.FromSeconds(1)); await commandRunner.WaitForRunAsync(); await service.StopAsync(CancellationToken.None); Assert.Contains(commandRunner.Commands, command => command.Arguments.Contains("meeting-assistant-pyannote-validation:local")); Assert.True(commandRunner.RunCancellationWasObserved); } private sealed class FakeLaunchProfileOptionsProvider : ILaunchProfileOptionsProvider { private readonly MeetingAssistantOptions options; public FakeLaunchProfileOptionsProvider(MeetingAssistantOptions options) { this.options = options; } public LaunchProfile GetRequiredProfile(string? name) { return new LaunchProfile(ConfigurationLaunchProfileOptionsProvider.DefaultProfileName, options); } public IReadOnlyList GetProfiles() { return [GetRequiredProfile(null)]; } public IReadOnlyList GetHotkeys() { return []; } } private sealed class BlockingCommandRunner : ICommandRunner { private readonly TaskCompletionSource runStarted = new(TaskCreationOptions.RunContinuationsAsynchronously); public IReadOnlyList Commands { get; private set; } = []; public bool RunCancellationWasObserved { get; private set; } public Task WaitForRunAsync() { return runStarted.Task.WaitAsync(TimeSpan.FromSeconds(5)); } public async Task RunAsync( string fileName, IReadOnlyList arguments, CancellationToken cancellationToken, IReadOnlyDictionary? environment = null) { Commands = Commands.Append(new CapturedCommand(fileName, arguments)).ToList(); if (arguments.Contains("inspect")) { return new CommandResult(0, "image-id", ""); } runStarted.TrySetResult(); try { await Task.Delay(Timeout.InfiniteTimeSpan, cancellationToken); } catch (OperationCanceledException) { RunCancellationWasObserved = true; throw; } return new CommandResult(0, "", ""); } } private sealed record CapturedCommand(string FileName, IReadOnlyList Arguments); }