Files
meeting-assistant/MeetingAssistant.Tests/DesktopCommunicationEndpointTests.cs
T
dh 728e66dd72
PR and Push Build/Test / windows-build-and-test (push) Failing after 38m25s
PR and Push Build/Test / portable-build-and-test (push) Successful in 8m9s
PR and Push Build/Test / macos-native-full (push) Skipped
ci: consolidate platform pipelines and test prerequisites on macOS support
2026-10-06 09:02:19 +02:00

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