forked from Manuel/meeting-assistant
ci: consolidate platform pipelines and test prerequisites on macOS support
This commit is contained in:
1 parent
1b19b08f2e
commit
728e66dd72
76 files changed
+7477
-239
No files matched your search
@@ -0,0 +1,222 @@
|
||||
using MeetingAssistant.MacOs;
|
||||
using MeetingAssistant.Workflow;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace MeetingAssistant.Tests;
|
||||
|
||||
public sealed class MacOsWorkflowRulesEditorSessionTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task ApprovedWorkflowStartsOnceInOwnSessionWithoutReplacingDefaultConversation()
|
||||
{
|
||||
var pipeline = new ReplyPipeline();
|
||||
using var lifetime = new TestApplicationLifetime();
|
||||
var sessions = CreateSessions(pipeline, lifetime);
|
||||
Assert.True(sessions.TryGetSession(null, out var normal));
|
||||
await normal.SendAsync("My existing conversation", CancellationToken.None);
|
||||
const string prompt = "Given Alpha in the title,\nwhen metadata arrives, then associate project Alpha.";
|
||||
|
||||
sessions.OpenWithPrompt(prompt);
|
||||
|
||||
var window = Assert.Single(sessions.GetAgentWindows());
|
||||
Assert.EndsWith($"/diagnostics/settings-and-logs?session={window.Id}", window.Url);
|
||||
Assert.True(sessions.TryGetSession(window.Id, out var requested));
|
||||
var snapshot = await WaitForCompletionAsync(requested);
|
||||
Assert.Equal(
|
||||
[new MacOsWorkflowRulesEditorMessage("user", prompt), new MacOsWorkflowRulesEditorMessage("agent", "Agent response.")],
|
||||
snapshot.Messages);
|
||||
for (var index = 0; index < 3; index++)
|
||||
{
|
||||
Assert.Equal(snapshot.Messages, requested.GetSnapshot().Messages);
|
||||
Assert.False(requested.GetSnapshot().IsThinking);
|
||||
Assert.Single(sessions.GetAgentWindows());
|
||||
}
|
||||
Assert.Equal(
|
||||
[new MacOsWorkflowRulesEditorMessage("user", "My existing conversation"), new MacOsWorkflowRulesEditorMessage("agent", "Agent response.")],
|
||||
normal.GetSnapshot().Messages);
|
||||
Assert.NotSame(normal, requested);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ClosingAResponseConnectionKeepsAcceptedChatWorkingAndSnapshotsRemainReadable()
|
||||
{
|
||||
var pipeline = new BlockingPipeline();
|
||||
using var lifetime = new TestApplicationLifetime();
|
||||
var sessions = CreateSessions(pipeline, lifetime);
|
||||
Assert.True(sessions.TryGetSession(null, out var session));
|
||||
using var responseConnection = new CancellationTokenSource();
|
||||
var response = session.SendAsync("Continue the approved change", responseConnection.Token);
|
||||
await pipeline.Started.Task.WaitAsync(TimeSpan.FromSeconds(2));
|
||||
var pending = session.GetSnapshot();
|
||||
Assert.True(pending.IsThinking);
|
||||
Assert.Equal([new MacOsWorkflowRulesEditorMessage("user", "Continue the approved change")], pending.Messages);
|
||||
|
||||
responseConnection.Cancel();
|
||||
await Assert.ThrowsAnyAsync<OperationCanceledException>(() => response);
|
||||
|
||||
Assert.True(session.GetSnapshot().IsThinking);
|
||||
pipeline.Release.TrySetResult();
|
||||
var completed = await WaitForCompletionAsync(session);
|
||||
Assert.False(completed.IsThinking);
|
||||
Assert.Equal("Agent response.", completed.Messages.Last().Content);
|
||||
Assert.Single(pending.Messages);
|
||||
Assert.True(pending.IsThinking);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ApprovedWorkflowWindowOpensWithoutWaitingForItsAgentResponse()
|
||||
{
|
||||
var pipeline = new BlockingPipeline();
|
||||
using var lifetime = new TestApplicationLifetime();
|
||||
var sessions = CreateSessions(pipeline, lifetime);
|
||||
IWorkflowRulesEditorWindowService editor = new MacOsWorkflowRulesEditorWindowService(
|
||||
Options.Create(new MeetingAssistantOptions()), () => sessions);
|
||||
try
|
||||
{
|
||||
await Task.Run(() => editor.ShowWithPrompt("Apply the approved workflow scenario."))
|
||||
.WaitAsync(TimeSpan.FromSeconds(2));
|
||||
await pipeline.Started.Task.WaitAsync(TimeSpan.FromSeconds(2));
|
||||
var window = Assert.Single(sessions.GetAgentWindows());
|
||||
Assert.True(sessions.TryGetSession(window.Id, out var session));
|
||||
Assert.True(session.GetSnapshot().IsThinking);
|
||||
}
|
||||
finally
|
||||
{
|
||||
pipeline.Release.TrySetResult();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConstructingTheWindowServiceDoesNotStartOrResolveAnAgentConversation()
|
||||
{
|
||||
using var lifetime = new TestApplicationLifetime();
|
||||
var sessions = CreateSessions(new ReplyPipeline(), lifetime);
|
||||
var resolutions = 0;
|
||||
IWorkflowRulesEditorWindowService editor = new MacOsWorkflowRulesEditorWindowService(
|
||||
Options.Create(new MeetingAssistantOptions()), () =>
|
||||
{
|
||||
resolutions++;
|
||||
return sessions;
|
||||
});
|
||||
|
||||
Assert.Equal(0, resolutions);
|
||||
Assert.Empty(sessions.GetAgentWindows());
|
||||
|
||||
editor.ShowWithPrompt("An explicitly approved scenario.");
|
||||
|
||||
Assert.Equal(1, resolutions);
|
||||
Assert.Single(sessions.GetAgentWindows());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TwoApprovedWorkflowsAndTheirFollowUpsRemainIndependent()
|
||||
{
|
||||
using var lifetime = new TestApplicationLifetime();
|
||||
var sessions = CreateSessions(new ReplyPipeline(), lifetime);
|
||||
sessions.OpenWithPrompt("First approved scenario.");
|
||||
sessions.OpenWithPrompt("Second approved scenario.");
|
||||
var windows = sessions.GetAgentWindows();
|
||||
Assert.Equal(2, windows.Count);
|
||||
Assert.Equal(2, windows.Select(window => window.Id).Distinct().Count());
|
||||
var conversations = new List<MacOsWorkflowRulesEditorSession>();
|
||||
foreach (var window in windows)
|
||||
{
|
||||
Assert.True(sessions.TryGetSession(window.Id, out var conversation));
|
||||
conversations.Add(conversation);
|
||||
}
|
||||
var snapshots = await Task.WhenAll(conversations.Select(WaitForCompletionAsync));
|
||||
Assert.Equal(
|
||||
["First approved scenario.", "Second approved scenario."],
|
||||
snapshots.Select(snapshot => snapshot.Messages[0].Content).Order());
|
||||
|
||||
var updated = await conversations[0].SendAsync("A follow-up in just this conversation.", CancellationToken.None);
|
||||
|
||||
Assert.Equal(4, updated.Messages.Count);
|
||||
Assert.Equal(2, conversations[1].GetSnapshot().Messages.Count);
|
||||
Assert.True(sessions.TryGetSession(null, out var normal));
|
||||
Assert.Empty(normal.GetSnapshot().Messages);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ApplicationShutdownCancelsBackgroundConversationAndPreventsNewWindows()
|
||||
{
|
||||
var pipeline = new BlockingPipeline();
|
||||
using var lifetime = new TestApplicationLifetime();
|
||||
var sessions = CreateSessions(pipeline, lifetime);
|
||||
sessions.OpenWithPrompt("Apply the approved scenario.");
|
||||
await pipeline.Started.Task.WaitAsync(TimeSpan.FromSeconds(2));
|
||||
var window = Assert.Single(sessions.GetAgentWindows());
|
||||
Assert.True(sessions.TryGetSession(window.Id, out var conversation));
|
||||
|
||||
lifetime.StopApplication();
|
||||
|
||||
using var deadline = new CancellationTokenSource(TimeSpan.FromSeconds(2));
|
||||
while (conversation.GetSnapshot().IsThinking) await Task.Delay(10, deadline.Token);
|
||||
Assert.Empty(sessions.GetAgentWindows());
|
||||
Assert.Single(conversation.GetSnapshot().Messages);
|
||||
Assert.ThrowsAny<OperationCanceledException>(() => sessions.OpenWithPrompt("A stale approval."));
|
||||
}
|
||||
|
||||
private static MacOsWorkflowRulesEditorSessions CreateSessions(
|
||||
IWorkflowRulesEditorChatPipeline pipeline,
|
||||
IHostApplicationLifetime lifetime) => new(
|
||||
pipeline,
|
||||
Options.Create(new MeetingAssistantOptions()),
|
||||
lifetime,
|
||||
NullLogger<MacOsWorkflowRulesEditorSessions>.Instance);
|
||||
|
||||
private static async Task<MacOsWorkflowRulesEditorChatResponse> WaitForCompletionAsync(
|
||||
MacOsWorkflowRulesEditorSession session)
|
||||
{
|
||||
using var deadline = new CancellationTokenSource(TimeSpan.FromSeconds(2));
|
||||
while (true)
|
||||
{
|
||||
var snapshot = session.GetSnapshot();
|
||||
if (snapshot.Messages.Count >= 2 && !snapshot.IsThinking) return snapshot;
|
||||
await Task.Delay(10, deadline.Token);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ReplyPipeline : IWorkflowRulesEditorChatPipeline
|
||||
{
|
||||
public Task<WorkflowRulesEditorChatResult> SendAsync(
|
||||
IReadOnlyList<WorkflowRulesEditorChatMessage> conversation,
|
||||
string userMessage,
|
||||
CancellationToken cancellationToken,
|
||||
Action<WorkflowRulesEditorActivityUpdate>? activityChanged = null) =>
|
||||
Task.FromResult(new WorkflowRulesEditorChatResult("Agent response."));
|
||||
}
|
||||
|
||||
private sealed class BlockingPipeline : IWorkflowRulesEditorChatPipeline
|
||||
{
|
||||
public TaskCompletionSource Started { get; } = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
public TaskCompletionSource Release { get; } = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
public async Task<WorkflowRulesEditorChatResult> SendAsync(
|
||||
IReadOnlyList<WorkflowRulesEditorChatMessage> conversation,
|
||||
string userMessage,
|
||||
CancellationToken cancellationToken,
|
||||
Action<WorkflowRulesEditorActivityUpdate>? activityChanged = null)
|
||||
{
|
||||
Started.TrySetResult();
|
||||
await Release.Task.WaitAsync(cancellationToken);
|
||||
return new WorkflowRulesEditorChatResult("Agent response.");
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class TestApplicationLifetime : IHostApplicationLifetime, IDisposable
|
||||
{
|
||||
private readonly CancellationTokenSource stopping = new();
|
||||
public CancellationToken ApplicationStarted => CancellationToken.None;
|
||||
public CancellationToken ApplicationStopping => stopping.Token;
|
||||
public CancellationToken ApplicationStopped => stopping.Token;
|
||||
public void StopApplication() => stopping.Cancel();
|
||||
public void Dispose()
|
||||
{
|
||||
stopping.Cancel();
|
||||
stopping.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user