Public Access
199 lines
6.5 KiB
C#
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.");
|
|
}
|
|
}
|
|
}
|