Public Access
Archive summary refinements and profile switching
PR and Push Build/Test / build-and-test (push) Successful in 6m37s
PR and Push Build/Test / build-and-test (push) Successful in 6m37s
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user