forked from Manuel/meeting-assistant
167 lines
7.6 KiB
C#
167 lines
7.6 KiB
C#
using System.Net;
|
|
using System.Net.Http.Json;
|
|
using MeetingAssistant.Notifications;
|
|
using MeetingAssistant.MacOs;
|
|
using MeetingAssistant.Workflow;
|
|
using Microsoft.AspNetCore.Hosting;
|
|
using Microsoft.AspNetCore.Mvc.Testing;
|
|
using Microsoft.AspNetCore.TestHost;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
|
|
|
namespace MeetingAssistant.Tests;
|
|
|
|
#if !WINDOWS
|
|
public sealed class DesktopCommunicationEndpointTests
|
|
{
|
|
[Fact]
|
|
public async Task PendingRequestCanBeReadThroughDesktopEndpoint()
|
|
{
|
|
await using var factory = CreateFactory();
|
|
using var client = factory.CreateClient();
|
|
var requests = factory.Services.GetRequiredService<DesktopUserRequestStore>();
|
|
var registration = requests.Add(new DesktopUserRequestSpec(
|
|
"agent-approval", "Project association", "Allow access to project Apollo?",
|
|
TimeSpan.FromMinutes(10), [new("yes", "Yes"), new("no", "No")]), CancellationToken.None);
|
|
|
|
using var response = await client.GetAsync("/desktop/requests");
|
|
|
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
|
var request = Assert.Single((await response.Content.ReadFromJsonAsync<DesktopUserRequest[]>())!);
|
|
Assert.Equal(registration.Id, request.Id);
|
|
Assert.Equal("Allow access to project Apollo?", request.Message);
|
|
Assert.Equal(new[] { "yes", "no" }, request.Actions.Select(action => action.Id));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task OfferedHttpResponseDeliversOneDecisionAndRejectsDuplicate()
|
|
{
|
|
await using var factory = CreateFactory();
|
|
using var client = factory.CreateClient();
|
|
var requests = factory.Services.GetRequiredService<DesktopUserRequestStore>();
|
|
var calls = 0;
|
|
var registration = requests.Add(new DesktopUserRequestSpec(
|
|
"agent-approval", "Approval", "Apply proposed change?", TimeSpan.FromMinutes(10),
|
|
[new("yes", "Yes"), new("no", "No")]), CancellationToken.None,
|
|
(_, _) => { Interlocked.Increment(ref calls); return Task.CompletedTask; });
|
|
var path = $"/desktop/requests/{registration.Id}/respond";
|
|
|
|
using var accepted = await client.PostAsJsonAsync(path, new { action = "yes" });
|
|
using var duplicate = await client.PostAsJsonAsync(path, new { action = "yes" });
|
|
|
|
Assert.Equal(HttpStatusCode.OK, accepted.StatusCode);
|
|
Assert.Equal(HttpStatusCode.Gone, duplicate.StatusCode);
|
|
Assert.Equal("yes", await registration.Decision);
|
|
Assert.Equal(1, calls);
|
|
Assert.Empty((await client.GetFromJsonAsync<DesktopUserRequest[]>("/desktop/requests"))!);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ApprovedWindowAndRepeatedSnapshotsKeepOneIndependentPrompt()
|
|
{
|
|
await using var factory = CreateFactory();
|
|
using var client = factory.CreateClient();
|
|
var sessions = factory.Services.GetRequiredService<MacOsWorkflowRulesEditorSessions>();
|
|
sessions.OpenWithPrompt("Add the requested workflow scenario in full.");
|
|
|
|
using var response = await client.GetAsync("/desktop/agent-windows");
|
|
|
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
|
var window = Assert.Single((await response.Content.ReadFromJsonAsync<MacOsAgentWindowRequest[]>())!);
|
|
Assert.EndsWith($"?session={window.Id}", window.Url);
|
|
var path = $"/diagnostics/settings-and-logs/chat?session={window.Id}";
|
|
MacOsWorkflowRulesEditorChatResponse? first = null;
|
|
for (var attempt = 0; attempt < 100; attempt++)
|
|
{
|
|
first = await client.GetFromJsonAsync<MacOsWorkflowRulesEditorChatResponse>(path);
|
|
if (!first!.IsThinking && first.Messages.Count > 0) break;
|
|
await Task.Delay(10);
|
|
}
|
|
Assert.False(first!.IsThinking);
|
|
var second = await client.GetFromJsonAsync<MacOsWorkflowRulesEditorChatResponse>(path);
|
|
Assert.Equal("Add the requested workflow scenario in full.", first.Messages[0].Content);
|
|
Assert.Equal(first.Messages, second!.Messages);
|
|
Assert.Single(factory.Services.GetRequiredService<EndpointVerificationPipeline>().Calls);
|
|
Assert.Empty((await client.GetFromJsonAsync<MacOsWorkflowRulesEditorChatResponse>(
|
|
"/diagnostics/settings-and-logs/chat"))!.Messages);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UnknownChatSessionCannotSendIntoTheDefaultConversation()
|
|
{
|
|
await using var factory = CreateFactory();
|
|
using var client = factory.CreateClient();
|
|
|
|
using var response = await client.PostAsJsonAsync(
|
|
"/diagnostics/settings-and-logs/chat?session=unknown-session", new { message = "Do not route this elsewhere" });
|
|
|
|
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
|
Assert.Empty(factory.Services.GetRequiredService<EndpointVerificationPipeline>().Calls);
|
|
Assert.Empty((await client.GetFromJsonAsync<MacOsWorkflowRulesEditorChatResponse>(
|
|
"/diagnostics/settings-and-logs/chat"))!.Messages);
|
|
}
|
|
|
|
[MacOsFact]
|
|
public async Task MacOsCompositionWaitsForApprovalAndUsesTheDesktopDecision()
|
|
{
|
|
await using var factory = CreateFactory();
|
|
using var client = factory.CreateClient();
|
|
using var origin = new CancellationTokenSource();
|
|
var approval = factory.Services.GetRequiredService<AgentApprovalService>();
|
|
var requests = factory.Services.GetRequiredService<DesktopUserRequestStore>();
|
|
var decision = approval.ConfirmAsync("Test approval", "Isolated composition check", origin.Token);
|
|
try
|
|
{
|
|
var pending = Assert.Single(requests.List());
|
|
Assert.False(decision.IsCompleted);
|
|
using var response = await client.PostAsJsonAsync(
|
|
$"/desktop/requests/{pending.Id}/respond", new { action = "no" });
|
|
|
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
|
Assert.False(await decision.WaitAsync(TimeSpan.FromSeconds(5)));
|
|
Assert.IsType<MacOsMeetingInactivityPromptService>(
|
|
factory.Services.GetRequiredService<MeetingAssistant.Recording.IMeetingInactivityPromptService>());
|
|
}
|
|
finally
|
|
{
|
|
origin.Cancel();
|
|
}
|
|
}
|
|
|
|
internal static WebApplicationFactory<Program> CreateFactory()
|
|
{
|
|
return new IsolatedApplicationFactory().WithWebHostBuilder(builder =>
|
|
{
|
|
builder.ConfigureAppConfiguration((_, configuration) =>
|
|
configuration.AddInMemoryCollection(new Dictionary<string, string?>
|
|
{
|
|
["MeetingAssistant:FunAsr:Backend:Enabled"] = "false",
|
|
["MeetingAssistant:CalendarRecordingPrompts:Enabled"] = "false"
|
|
}));
|
|
builder.ConfigureTestServices(services =>
|
|
{
|
|
services.RemoveAll<IWorkflowRulesEditorChatPipeline>();
|
|
services.AddSingleton<EndpointVerificationPipeline>();
|
|
services.AddSingleton<IWorkflowRulesEditorChatPipeline>(provider =>
|
|
provider.GetRequiredService<EndpointVerificationPipeline>());
|
|
});
|
|
});
|
|
}
|
|
|
|
private sealed class EndpointVerificationPipeline : IWorkflowRulesEditorChatPipeline
|
|
{
|
|
public List<string> Calls { get; } = [];
|
|
|
|
public Task<WorkflowRulesEditorChatResult> SendAsync(
|
|
IReadOnlyList<WorkflowRulesEditorChatMessage> conversation,
|
|
string userMessage, CancellationToken cancellationToken,
|
|
Action<WorkflowRulesEditorActivityUpdate>? activityChanged = null)
|
|
{
|
|
Calls.Add(userMessage);
|
|
return Task.FromResult(new WorkflowRulesEditorChatResult("Verification reply"));
|
|
}
|
|
}
|
|
}
|
|
#endif
|