using MeetingAssistant.Speakers; using Microsoft.Data.Sqlite; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; namespace MeetingAssistant.Tests; public sealed class SpeakerRecognitionFeatureSelectionTests { [Theory] [InlineData(false, typeof(SpeakerIdentityService), typeof(SpeakerIdentityMergeService), 1, false)] [InlineData(true, typeof(ResemblyzerSpeakerIdentificationService), typeof(ResemblyzerSpeakerIdentityMergeService), 1000, true)] public async Task FeatureFlagSelectsExactlyOneIdentificationBackend( bool enabled, Type expectedIdentificationType, Type expectedMergeType, int expectedMinimumSamples, bool expectedNonOverlappingSamples) { var directory = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N")); Directory.CreateDirectory(directory); try { await using var factory = new WebApplicationFactory() .WithWebHostBuilder(builder => { builder.ConfigureAppConfiguration((_, configuration) => { configuration.AddInMemoryCollection(new Dictionary { ["MeetingAssistant:FunAsr:Backend:Enabled"] = "false", ["MeetingAssistant:SpeakerIdentification:DatabasePath"] = Path.Combine(directory, "identities.db"), ["MeetingAssistant:SpeakerIdentification:PyannoteValidation:Enabled"] = "false", ["MeetingAssistant:SpeakerIdentification:Resemblyzer:Enabled"] = enabled.ToString() }); }); builder.ConfigureTestServices(services => { services.RemoveAll(); services.AddSingleton(); }); }); var service = factory.Services.GetRequiredService(); Assert.IsType(expectedIdentificationType, service); Assert.Single(factory.Services.GetServices()); var mergeService = factory.Services.GetRequiredService(); Assert.IsType(expectedMergeType, mergeService); Assert.Single(factory.Services.GetServices()); var policy = factory.Services.GetRequiredService(); Assert.Equal(expectedMinimumSamples, policy.MinimumRetainedSamples); Assert.Equal(expectedNonOverlappingSamples, policy.RequireNonOverlappingSamples); } finally { SqliteConnection.ClearAllPools(); Directory.Delete(directory, recursive: true); } } private sealed class NoopEncoder : IResemblyzerVoiceEncoder { public Task> EncodeAsync( IReadOnlyList wavSamples, CancellationToken cancellationToken) { return Task.FromResult>([]); } public Task WarmUpAsync(CancellationToken cancellationToken) { return Task.CompletedTask; } } }