forked from Manuel/meeting-assistant
Merge remote-tracking branch 'refs/remotes/fork/codex/macos-support' into codex/macos-support
# Conflicts: # MeetingAssistant/Program.cs # README.md
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using MeetingAssistant.Recording;
|
||||
using MeetingAssistant.Workflow;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc.Testing;
|
||||
using Microsoft.AspNetCore.TestHost;
|
||||
@@ -54,6 +55,17 @@ public sealed class HealthEndpointTests : IClassFixture<WebApplicationFactory<Pr
|
||||
Assert.Equal(HostOperatingSystem.DetectCurrent(), first);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task NeutralBuildReportsSpeakerPlaybackUnavailable()
|
||||
{
|
||||
var playback = factory.Services.GetRequiredService<IWorkflowRulesEditorSamplePlaybackQueue>();
|
||||
|
||||
var result = await playback.QueueAsync(1, [1, 2]);
|
||||
|
||||
Assert.Contains("Windows build", result);
|
||||
Assert.DoesNotContain("Queued", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ApplicationStartupDeletesStaleTemporaryRecordings()
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@ using MeetingAssistant.Transcription;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc.Testing;
|
||||
using Microsoft.AspNetCore.TestHost;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
@@ -170,7 +171,7 @@ public sealed class MacOsMeetingAudioSourceTests
|
||||
microphonePcm: Pcm16(2_000),
|
||||
systemPcm: Pcm16(10_000));
|
||||
var speechPipelines = new CapturingSpeechRecognitionPipelineFactory();
|
||||
await using var factory = new WebApplicationFactory<Program>().WithWebHostBuilder(builder =>
|
||||
var factory = new WebApplicationFactory<Program>().WithWebHostBuilder(builder =>
|
||||
{
|
||||
builder.ConfigureAppConfiguration((_, configuration) =>
|
||||
{
|
||||
@@ -225,6 +226,9 @@ public sealed class MacOsMeetingAudioSourceTests
|
||||
}
|
||||
finally
|
||||
{
|
||||
await factory.DisposeAsync();
|
||||
using var connection = new SqliteConnection($"Data Source={Path.Combine(testRoot, "speakers.db")}");
|
||||
SqliteConnection.ClearPool(connection);
|
||||
if (Directory.Exists(testRoot))
|
||||
{
|
||||
Directory.Delete(testRoot, recursive: true);
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.12" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.9.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.10.1" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="4.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -2432,12 +2432,17 @@ public sealed class RecordingCoordinatorTests
|
||||
await coordinator.StopAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ToggleToDifferentLaunchProfileBuffersAudioCapturedWhilePreviousPipelineDrains()
|
||||
[Theory]
|
||||
[InlineData("default", "english")]
|
||||
[InlineData("english", "default")]
|
||||
public async Task ProfileSwitchDrainsFinalTranscriptAndResumesBufferedAudioWithoutBlockingNotifications(
|
||||
string initialProfile,
|
||||
string targetProfile)
|
||||
{
|
||||
var audioSource = new ControlledAudioSource();
|
||||
var pipelineFactory = new BlockingProfileSwitchSpeechRecognitionPipelineFactory();
|
||||
var transcriptStore = new InMemoryTranscriptStore();
|
||||
var promptService = new IgnoringMeetingInactivityPromptService();
|
||||
var launchProfiles = CreateLaunchProfiles(
|
||||
Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N"), "default"),
|
||||
Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N"), "english"));
|
||||
@@ -2452,13 +2457,16 @@ public sealed class RecordingCoordinatorTests
|
||||
new CapturingMeetingSummaryPipeline(),
|
||||
Options.Create(launchProfiles.GetRequiredProfile(null).Options),
|
||||
NullLogger<MeetingRecordingCoordinator>.Instance,
|
||||
inactivityPromptService: promptService,
|
||||
launchProfiles: launchProfiles);
|
||||
|
||||
await coordinator.StartAsync(CancellationToken.None);
|
||||
await coordinator.StartAsync(initialProfile, CancellationToken.None);
|
||||
await audioSource.WriteAsync(new AudioChunk([1, 0], 16000, 1), CancellationToken.None);
|
||||
await transcriptStore.WaitForTextAsync("default chunk:2");
|
||||
await transcriptStore.WaitForTextAsync($"{initialProfile} chunk:2");
|
||||
await WaitUntilAsync(() => promptService.DismissAllCount == 1);
|
||||
|
||||
var switchTask = coordinator.ToggleAsync("english", CancellationToken.None);
|
||||
using var switchCancellation = new CancellationTokenSource(TimeSpan.FromSeconds(10));
|
||||
var switchTask = coordinator.ToggleAsync(targetProfile, switchCancellation.Token);
|
||||
await pipelineFactory.WaitUntilFirstPipelineDrainIsBlockedAsync();
|
||||
await audioSource.WriteAsync(new AudioChunk([1, 0, 2, 0, 3, 0], 16000, 1), CancellationToken.None);
|
||||
|
||||
@@ -2466,10 +2474,21 @@ public sealed class RecordingCoordinatorTests
|
||||
|
||||
pipelineFactory.ReleaseFirstPipelineDrain();
|
||||
await switchTask.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
await transcriptStore.WaitForTextAsync("english chunk:6");
|
||||
await transcriptStore.WaitForTextAsync($"{targetProfile} chunk:6");
|
||||
await WaitUntilAsync(() => promptService.DismissAllCount == 3);
|
||||
|
||||
Assert.Equal([null, "english"], pipelineFactory.ProfileNames);
|
||||
await coordinator.StopAsync(CancellationToken.None);
|
||||
Assert.Equal(targetProfile, coordinator.CurrentStatus.LaunchProfile);
|
||||
Assert.Equal([initialProfile, targetProfile], pipelineFactory.ProfileNames);
|
||||
var segments = transcriptStore.Segments.ToList();
|
||||
var finalResultIndex = segments.FindIndex(segment => segment.Text == "Final result emitted while switching profiles.");
|
||||
var markerIndex = segments.FindIndex(segment => segment.Text.Contains($"Transcription profile changed to {targetProfile}", StringComparison.Ordinal));
|
||||
Assert.True(finalResultIndex >= 0 && finalResultIndex < markerIndex);
|
||||
|
||||
await coordinator.ToggleAsync(initialProfile, CancellationToken.None).WaitAsync(TimeSpan.FromSeconds(5));
|
||||
await audioSource.WriteAsync(new AudioChunk([1, 0, 2, 0], 16000, 1), CancellationToken.None);
|
||||
await transcriptStore.WaitForTextAsync($"{initialProfile} chunk:4");
|
||||
await coordinator.StopAsync(CancellationToken.None).WaitAsync(TimeSpan.FromSeconds(5));
|
||||
Assert.False(coordinator.CurrentStatus.IsRecording);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -5186,6 +5205,11 @@ public sealed class RecordingCoordinatorTests
|
||||
|
||||
drainBlocked.TrySetResult();
|
||||
await releaseDrain.Task.WaitAsync(cancellationToken);
|
||||
yield return new TranscriptionSegment(
|
||||
TimeSpan.Zero,
|
||||
TimeSpan.FromSeconds(3),
|
||||
"Unknown",
|
||||
"Final result emitted while switching profiles.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user