using MeetingAssistant.LaunchProfiles; using MeetingAssistant.Recording; using MeetingAssistant.Transcription; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; namespace MeetingAssistant.Tests; public sealed class ConfiguredSpeechRecognitionPipelineFactoryTests { [Fact] public async Task FactoryCreatesAzureSpeechPipelineWhenConfigured() { var services = new ServiceCollection(); services.AddSingleton(Options.Create(new MeetingAssistantOptions { Recording = new RecordingOptions { TranscriptionProvider = "azure-speech" } })); services.AddSingleton>( NullLogger.Instance); services.AddTransient(); var provider = services.BuildServiceProvider(); var factory = new ConfiguredSpeechRecognitionPipelineFactory( [new AzureSpeechRecognitionPipelineBuilder(provider)], provider.GetRequiredService>()); await using var pipeline = factory.Create(); Assert.IsType(pipeline); } [Fact] public async Task FactoryPassesResolvedLaunchProfileOptionsToSelectedBuilder() { var services = new ServiceCollection().BuildServiceProvider(); var builder = new CapturingPipelineBuilder(); var launchProfiles = new ConfigurationLaunchProfileOptionsProvider( new ConfigurationBuilder() .AddInMemoryCollection(new Dictionary { ["MeetingAssistant:Recording:TranscriptionProvider"] = "capturing", ["MeetingAssistant:Agent:Model"] = "default-model", ["MeetingAssistant:LaunchProfiles:english:Hotkey:Toggle"] = "Ctrl+Alt+L", ["MeetingAssistant:LaunchProfiles:english:Recording:TranscriptionProvider"] = "capturing", ["MeetingAssistant:LaunchProfiles:english:Agent:Model"] = "english-model" }) .Build()); var factory = new ConfiguredSpeechRecognitionPipelineFactory( [builder], Options.Create(launchProfiles.GetRequiredProfile(null).Options), launchProfiles); await using var pipeline = factory.Create("english"); Assert.IsType(pipeline); Assert.Equal("english-model", builder.Options?.Agent.Model); } private sealed class CapturingPipelineBuilder : ISpeechRecognitionPipelineBuilder { public string ProviderName => "capturing"; public MeetingAssistantOptions? Options { get; private set; } public ISpeechRecognitionPipeline Create(MeetingAssistantOptions options) { Options = options; return new CapturingPipeline(); } } private sealed class CapturingPipeline : ISpeechRecognitionPipeline { public Task InitializeAsync(CancellationToken cancellationToken) { return Task.CompletedTask; } public Task InitializeAsync( SpeechRecognitionPipelineOptions options, CancellationToken cancellationToken) { return Task.CompletedTask; } public Task WaitUntilReadyAsync(CancellationToken cancellationToken) { return Task.CompletedTask; } public ValueTask WriteAsync(AudioChunk chunk, CancellationToken cancellationToken) { return ValueTask.CompletedTask; } public Task CompleteAsync(CancellationToken cancellationToken) { return Task.CompletedTask; } public async IAsyncEnumerable ReadLiveTranscriptAsync( [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken) { await Task.CompletedTask; yield break; } public Task> ReadFinishedTranscriptAsync( string audioPath, SpeechRecognitionPipelineOptions options, CancellationToken cancellationToken) { return Task.FromResult>([]); } public ValueTask DisposeAsync() { return ValueTask.CompletedTask; } } }