Files
meeting-assistant/MeetingAssistant.Tests/MeetingSummaryRetryRunnerTests.cs
codex 7777b349a5
PR and Push Build/Test / build-and-test (push) Successful in 6m37s
Archive summary refinements and profile switching
2026-05-27 23:11:21 +02:00

199 lines
6.5 KiB
C#

using MeetingAssistant.MeetingNotes;
using MeetingAssistant.Summary;
using Microsoft.Extensions.Logging.Abstractions;
namespace MeetingAssistant.Tests;
public sealed class MeetingSummaryRetryRunnerTests
{
[Fact]
public async Task TriggerStartsRetryInBackgroundAndTransitionsAssistantContextState()
{
var artifacts = new MeetingSessionArtifacts(
"meeting.md",
"transcript.md",
"context.md",
"summary.md");
var resolver = new FixedSummaryArtifactResolver(artifacts);
var pipeline = new BlockingSummaryPipeline();
var artifactStore = new CapturingArtifactStore();
var runner = new MeetingSummaryRetryRunner(
resolver,
pipeline,
artifactStore,
NullLogger<MeetingSummaryRetryRunner>.Instance);
var result = await runner.TriggerAsync(
"summary.md",
new MeetingAssistantOptions(),
CancellationToken.None);
Assert.NotNull(result);
Assert.Equal("summary.md", result.SummaryPath);
Assert.False(pipeline.Completed);
await pipeline.WaitUntilStartedAsync();
Assert.Equal([AssistantContextState.Summarizing], artifactStore.States);
pipeline.CompleteSuccessfully();
await artifactStore.WaitForStateCountAsync(2);
Assert.Equal(
[AssistantContextState.Summarizing, AssistantContextState.Finished],
artifactStore.States);
}
[Fact]
public async Task TriggerMarksAssistantContextErrorWhenRetryFails()
{
var artifacts = new MeetingSessionArtifacts(
"meeting.md",
"transcript.md",
"context.md",
"summary.md");
var resolver = new FixedSummaryArtifactResolver(artifacts);
var pipeline = new BlockingSummaryPipeline();
var artifactStore = new CapturingArtifactStore();
var runner = new MeetingSummaryRetryRunner(
resolver,
pipeline,
artifactStore,
NullLogger<MeetingSummaryRetryRunner>.Instance);
await runner.TriggerAsync(
"summary.md",
new MeetingAssistantOptions(),
CancellationToken.None);
await pipeline.WaitUntilStartedAsync();
pipeline.CompleteWithError();
await artifactStore.WaitForStateCountAsync(2);
Assert.Equal(
[AssistantContextState.Summarizing, AssistantContextState.Error],
artifactStore.States);
}
private sealed class FixedSummaryArtifactResolver : IMeetingSummaryArtifactResolver
{
private readonly MeetingSessionArtifacts? artifacts;
public FixedSummaryArtifactResolver(MeetingSessionArtifacts? artifacts)
{
this.artifacts = artifacts;
}
public Task<MeetingSessionArtifacts?> ResolveBySummaryPathAsync(
string summaryPath,
CancellationToken cancellationToken)
{
return Task.FromResult(artifacts);
}
public Task<MeetingSessionArtifacts?> ResolveBySummaryPathAsync(
string summaryPath,
MeetingAssistantOptions options,
CancellationToken cancellationToken)
{
return Task.FromResult(artifacts);
}
}
private sealed class BlockingSummaryPipeline : IMeetingSummaryPipeline
{
private readonly TaskCompletionSource started =
new(TaskCreationOptions.RunContinuationsAsynchronously);
private readonly TaskCompletionSource<MeetingSummaryRunResult> completion =
new(TaskCreationOptions.RunContinuationsAsynchronously);
public bool Completed { get; private set; }
public async Task<MeetingSummaryRunResult> RunAsync(
MeetingSessionArtifacts artifacts,
CancellationToken cancellationToken)
{
started.TrySetResult();
var result = await completion.Task.WaitAsync(cancellationToken);
Completed = true;
return result;
}
public Task WaitUntilStartedAsync()
{
return started.Task.WaitAsync(TimeSpan.FromSeconds(5));
}
public void CompleteSuccessfully()
{
completion.TrySetResult(new MeetingSummaryRunResult("summary.md", "ok"));
}
public void CompleteWithError()
{
completion.TrySetResult(new MeetingSummaryRunResult("summary.md", "failed", false, "failed"));
}
}
private sealed class CapturingArtifactStore : IMeetingArtifactStore
{
private TaskCompletionSource stateChanged =
new(TaskCreationOptions.RunContinuationsAsynchronously);
public List<AssistantContextState> States { get; } = [];
public Task CreateAssistantContextAsync(
MeetingSessionArtifacts artifacts,
MeetingNote meetingNote,
string agenda,
DateTimeOffset? scheduledEnd,
CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
public Task UpdateAssistantContextMetadataAsync(
MeetingSessionArtifacts artifacts,
MeetingNote meetingNote,
string agenda,
DateTimeOffset? scheduledEnd,
CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
public Task UpdateAssistantContextMeetingAsync(
MeetingSessionArtifacts artifacts,
MeetingNote meetingNote,
CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
public Task UpdateAssistantContextStateAsync(
MeetingSessionArtifacts artifacts,
AssistantContextState state,
CancellationToken cancellationToken)
{
States.Add(state);
var observed = stateChanged;
observed.TrySetResult();
stateChanged = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
return Task.CompletedTask;
}
public async Task WaitForStateCountAsync(int count)
{
var deadline = DateTimeOffset.UtcNow.AddSeconds(5);
while (DateTimeOffset.UtcNow < deadline)
{
if (States.Count >= count)
{
return;
}
await stateChanged.Task.WaitAsync(TimeSpan.FromMilliseconds(100));
}
throw new TimeoutException($"Expected {count} state changes.");
}
}
}