forked from Manuel/meeting-assistant
130 lines
6.8 KiB
C#
130 lines
6.8 KiB
C#
using MeetingAssistant.Calendar;
|
|
using MeetingAssistant.MacOs;
|
|
using MeetingAssistant.Notifications;
|
|
using MeetingAssistant.Recording;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
|
|
namespace MeetingAssistant.Tests;
|
|
|
|
public sealed class MacOsUserPromptTests
|
|
{
|
|
[Theory]
|
|
[InlineData("yes", true)]
|
|
[InlineData("no", false)]
|
|
public async Task ApprovalWaitsForAnExplicitDesktopDecision(string action, bool approved)
|
|
{
|
|
using var lifetime = new TestApplicationLifetime();
|
|
using var store = CreateStore(lifetime);
|
|
IAgentApprovalPromptService prompts = new MacOsAgentApprovalPromptService(store);
|
|
var decision = prompts.ConfirmAsync(new AgentApprovalPrompt("Project requested", "Associate Alpha?",
|
|
TimeSpan.FromMinutes(10)), CancellationToken.None);
|
|
Assert.False(decision.IsCompleted);
|
|
var request = Assert.Single(store.List());
|
|
Assert.Equal("agent-approval", request.Kind);
|
|
Assert.Equal(["yes", "no"], request.Actions.Select(item => item.Id));
|
|
Assert.Equal(DesktopUserRequestRespondResult.Accepted, await store.RespondAsync(request.Id, action));
|
|
Assert.Equal(approved, await decision);
|
|
Assert.Empty(store.List());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task InactivityPromptReturnsImmediatelyAndOffersPauseWithOriginLifetime()
|
|
{
|
|
using var lifetime = new TestApplicationLifetime();
|
|
using var origin = new CancellationTokenSource();
|
|
var clock = new ManualDesktopRequestTimeProvider();
|
|
using var store = new DesktopUserRequestStore(clock, lifetime, NullLogger<DesktopUserRequestStore>.Instance);
|
|
IMeetingInactivityPromptService prompts = new MacOsMeetingInactivityPromptService(store);
|
|
MeetingInactivityPromptResponse? received = null;
|
|
CancellationToken receivedToken = default;
|
|
var showing = prompts.ShowStopPromptAsync(
|
|
new MeetingInactivityPromptRequest(TimeSpan.FromMinutes(2), clock.GetUtcNow(), 1, TimeSpan.FromMinutes(2)),
|
|
(response, token) =>
|
|
{
|
|
received = response;
|
|
receivedToken = token;
|
|
return Task.CompletedTask;
|
|
}, origin.Token);
|
|
Assert.True(showing.IsCompletedSuccessfully);
|
|
var request = Assert.Single(store.List());
|
|
Assert.Equal("meeting-inactivity", request.Kind);
|
|
Assert.Equal(["stop", "continue", "pause"], request.Actions.Select(item => item.Id));
|
|
Assert.Equal(DateTimeOffset.Parse("2026-10-03T10:01:00Z"), request.ExpiresAt);
|
|
Assert.Equal(DesktopUserRequestRespondResult.Accepted, await store.RespondAsync(request.Id, "pause"));
|
|
Assert.Equal(MeetingInactivityPromptResponse.Pause, received);
|
|
Assert.Equal(origin.Token, receivedToken);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(true, "attach-metadata", MeetingStartPromptResponse.AttachMetadataToCurrentMeeting)]
|
|
[InlineData(false, "skip", MeetingStartPromptResponse.Skip)]
|
|
public async Task CalendarPromptReturnsImmediatelyAndPreservesOfferedActions(bool canAttach, string action, MeetingStartPromptResponse expected)
|
|
{
|
|
using var lifetime = new TestApplicationLifetime();
|
|
var clock = new ManualDesktopRequestTimeProvider();
|
|
using var store = new DesktopUserRequestStore(clock, lifetime, NullLogger<DesktopUserRequestStore>.Instance);
|
|
IMeetingStartPromptService prompts = new MacOsMeetingStartPromptService(store, clock);
|
|
var meeting = new CalendarMeeting("event-1", "Alpha planning", clock.GetUtcNow(), clock.GetUtcNow().AddMinutes(2));
|
|
MeetingStartPromptResponse? received = null;
|
|
var showing = prompts.ShowPromptAsync(new MeetingStartPromptRequest(meeting, canAttach),
|
|
(response, _) => { received = response; return Task.CompletedTask; }, CancellationToken.None);
|
|
Assert.True(showing.IsCompletedSuccessfully);
|
|
var request = Assert.Single(store.List());
|
|
Assert.Equal("meeting-start", request.Kind);
|
|
Assert.Contains("Alpha planning", request.Message);
|
|
Assert.Equal(DateTimeOffset.Parse("2026-10-03T10:02:00Z"), request.ExpiresAt);
|
|
Assert.Equal(canAttach, request.Actions.Any(item => item.Id == "attach-metadata"));
|
|
Assert.Equal(DesktopUserRequestRespondResult.Accepted, await store.RespondAsync(request.Id, action));
|
|
Assert.Equal(expected, received);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(false)]
|
|
[InlineData(true)]
|
|
public async Task ApprovalExpirationOrOriginCancellationNeverGrantsConsent(bool cancelOrigin)
|
|
{
|
|
using var lifetime = new TestApplicationLifetime();
|
|
using var origin = new CancellationTokenSource();
|
|
var clock = new ManualDesktopRequestTimeProvider();
|
|
using var store = new DesktopUserRequestStore(clock, lifetime, NullLogger<DesktopUserRequestStore>.Instance);
|
|
IAgentApprovalPromptService prompts = new MacOsAgentApprovalPromptService(store);
|
|
var decision = prompts.ConfirmAsync(new AgentApprovalPrompt("Workflow requested", "Update matching rule?",
|
|
TimeSpan.FromMinutes(10)), origin.Token);
|
|
var request = Assert.Single(store.List());
|
|
if (cancelOrigin)
|
|
{
|
|
await origin.CancelAsync();
|
|
await Assert.ThrowsAnyAsync<OperationCanceledException>(() => decision);
|
|
}
|
|
else
|
|
{
|
|
clock.Advance(TimeSpan.FromMinutes(10));
|
|
Assert.False(await decision);
|
|
}
|
|
Assert.Empty(store.List());
|
|
Assert.Equal(DesktopUserRequestRespondResult.NoLongerPending, await store.RespondAsync(request.Id, "yes"));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DismissingInactivityLeavesUnrelatedApprovalPending()
|
|
{
|
|
using var lifetime = new TestApplicationLifetime();
|
|
using var store = CreateStore(lifetime);
|
|
IAgentApprovalPromptService approvals = new MacOsAgentApprovalPromptService(store);
|
|
var decision = approvals.ConfirmAsync(new AgentApprovalPrompt("Project requested", "Associate Alpha?",
|
|
TimeSpan.FromMinutes(10)), CancellationToken.None);
|
|
var approval = Assert.Single(store.List());
|
|
IMeetingInactivityPromptService inactivity = new MacOsMeetingInactivityPromptService(store);
|
|
await inactivity.ShowStopPromptAsync(new MeetingInactivityPromptRequest(TimeSpan.FromMinutes(2),
|
|
DateTimeOffset.UtcNow, 1, TimeSpan.FromMinutes(2)), (_, _) => Task.CompletedTask, CancellationToken.None);
|
|
Assert.Equal(2, store.List().Count);
|
|
await inactivity.DismissAllAsync(CancellationToken.None);
|
|
Assert.Equal(approval.Id, Assert.Single(store.List()).Id);
|
|
await store.RespondAsync(approval.Id, "no");
|
|
Assert.False(await decision);
|
|
}
|
|
|
|
private static DesktopUserRequestStore CreateStore(TestApplicationLifetime lifetime) => new(
|
|
TimeProvider.System, lifetime, NullLogger<DesktopUserRequestStore>.Instance);
|
|
}
|