chore: import upstream snapshot with attribution
dotnet-build-and-test / dotnet-test-functions (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-foundry-hosted-it (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
dotnet-build-and-test / Integration Test Report (push) Has been cancelled
CodeQL / Analyze (csharp) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
dotnet-build-and-test / dotnet-test-functions (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-foundry-hosted-it (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
dotnet-build-and-test / Integration Test Report (push) Has been cancelled
CodeQL / Analyze (csharp) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
This commit is contained in:
+220
@@ -0,0 +1,220 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Moq;
|
||||
|
||||
namespace Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for the <see cref="AGUIEndpointRouteBuilderExtensions"/> class.
|
||||
/// </summary>
|
||||
public sealed class AGUIEndpointRouteBuilderExtensionsTests
|
||||
{
|
||||
[Fact]
|
||||
public void MapAGUIServer_MapsEndpoint_AtSpecifiedPattern()
|
||||
{
|
||||
// Arrange
|
||||
Mock<IEndpointRouteBuilder> endpointsMock = new();
|
||||
Mock<IServiceProvider> serviceProviderMock = new();
|
||||
serviceProviderMock.As<IKeyedServiceProvider>();
|
||||
|
||||
endpointsMock.Setup(e => e.ServiceProvider).Returns(serviceProviderMock.Object);
|
||||
endpointsMock.Setup(e => e.DataSources).Returns([]);
|
||||
|
||||
const string Pattern = "/api/agent";
|
||||
AIAgent agent = new TestAgent();
|
||||
|
||||
// Act
|
||||
IEndpointConventionBuilder? result = endpointsMock.Object.MapAGUIServer(Pattern, agent);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MapAGUIServer_WithAgentName_ResolvesKeyedAgentFromDI()
|
||||
{
|
||||
// Arrange
|
||||
Mock<IEndpointRouteBuilder> endpointsMock = new();
|
||||
Mock<IServiceProvider> serviceProviderMock = new();
|
||||
AIAgent agent = new NamedTestAgent();
|
||||
|
||||
serviceProviderMock.As<IKeyedServiceProvider>()
|
||||
.Setup(sp => sp.GetRequiredKeyedService(typeof(AIAgent), "test-agent"))
|
||||
.Returns(agent);
|
||||
|
||||
endpointsMock.Setup(e => e.ServiceProvider).Returns(serviceProviderMock.Object);
|
||||
endpointsMock.Setup(e => e.DataSources).Returns([]);
|
||||
|
||||
// Act
|
||||
IEndpointConventionBuilder? result = endpointsMock.Object.MapAGUIServer("test-agent", "/api/agent");
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
serviceProviderMock.As<IKeyedServiceProvider>()
|
||||
.Verify(sp => sp.GetRequiredKeyedService(typeof(AIAgent), "test-agent"), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MapAGUIServer_WithHostedAgentBuilder_ResolvesAgentByBuilderName()
|
||||
{
|
||||
// Arrange
|
||||
Mock<IEndpointRouteBuilder> endpointsMock = new();
|
||||
Mock<IServiceProvider> serviceProviderMock = new();
|
||||
Mock<IHostedAgentBuilder> agentBuilderMock = new();
|
||||
AIAgent agent = new NamedTestAgent();
|
||||
|
||||
agentBuilderMock.Setup(b => b.Name).Returns("test-agent");
|
||||
|
||||
serviceProviderMock.As<IKeyedServiceProvider>()
|
||||
.Setup(sp => sp.GetRequiredKeyedService(typeof(AIAgent), "test-agent"))
|
||||
.Returns(agent);
|
||||
|
||||
endpointsMock.Setup(e => e.ServiceProvider).Returns(serviceProviderMock.Object);
|
||||
endpointsMock.Setup(e => e.DataSources).Returns([]);
|
||||
|
||||
// Act
|
||||
IEndpointConventionBuilder? result = endpointsMock.Object.MapAGUIServer(agentBuilderMock.Object, "/api/agent");
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
serviceProviderMock.As<IKeyedServiceProvider>()
|
||||
.Verify(sp => sp.GetRequiredKeyedService(typeof(AIAgent), "test-agent"), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MapAGUIServer_WithAgent_ResolvesSessionStoreFromDI()
|
||||
{
|
||||
// Arrange
|
||||
Mock<IEndpointRouteBuilder> endpointsMock = new();
|
||||
Mock<IServiceProvider> serviceProviderMock = new();
|
||||
Mock<AgentSessionStore> sessionStoreMock = new();
|
||||
AIAgent agent = new NamedTestAgent();
|
||||
|
||||
serviceProviderMock.As<IKeyedServiceProvider>()
|
||||
.Setup(sp => sp.GetKeyedService(typeof(AgentSessionStore), "test-agent"))
|
||||
.Returns(sessionStoreMock.Object);
|
||||
|
||||
endpointsMock.Setup(e => e.ServiceProvider).Returns(serviceProviderMock.Object);
|
||||
endpointsMock.Setup(e => e.DataSources).Returns([]);
|
||||
|
||||
// Act
|
||||
IEndpointConventionBuilder? result = endpointsMock.Object.MapAGUIServer("/api/agent", agent);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
serviceProviderMock.As<IKeyedServiceProvider>()
|
||||
.Verify(sp => sp.GetKeyedService(typeof(AgentSessionStore), "test-agent"), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MapAGUIServer_WithoutSessionStore_FallsBackToNoopStore()
|
||||
{
|
||||
// Arrange
|
||||
Mock<IEndpointRouteBuilder> endpointsMock = new();
|
||||
Mock<IServiceProvider> serviceProviderMock = new();
|
||||
AIAgent agent = new TestAgent();
|
||||
|
||||
// No session store registered - IKeyedServiceProvider returns null by default
|
||||
serviceProviderMock.As<IKeyedServiceProvider>();
|
||||
|
||||
endpointsMock.Setup(e => e.ServiceProvider).Returns(serviceProviderMock.Object);
|
||||
endpointsMock.Setup(e => e.DataSources).Returns([]);
|
||||
|
||||
// Act - should not throw (falls back to NoopAgentSessionStore)
|
||||
IEndpointConventionBuilder? result = endpointsMock.Object.MapAGUIServer("/api/agent", agent);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MapAGUIServer_WithNullEndpoints_ThrowsArgumentNullException()
|
||||
{
|
||||
// Arrange
|
||||
AIAgent agent = new TestAgent();
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
AGUIEndpointRouteBuilderExtensions.MapAGUIServer(null!, "/api/agent", agent));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MapAGUIServer_WithNullAgent_ThrowsArgumentNullException()
|
||||
{
|
||||
// Arrange
|
||||
Mock<IEndpointRouteBuilder> endpointsMock = new();
|
||||
Mock<IServiceProvider> serviceProviderMock = new();
|
||||
serviceProviderMock.As<IKeyedServiceProvider>();
|
||||
endpointsMock.Setup(e => e.ServiceProvider).Returns(serviceProviderMock.Object);
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
endpointsMock.Object.MapAGUIServer("/api/agent", (AIAgent)null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MapAGUIServer_WithNullAgentName_ThrowsArgumentNullException()
|
||||
{
|
||||
// Arrange
|
||||
Mock<IEndpointRouteBuilder> endpointsMock = new();
|
||||
Mock<IServiceProvider> serviceProviderMock = new();
|
||||
serviceProviderMock.As<IKeyedServiceProvider>();
|
||||
endpointsMock.Setup(e => e.ServiceProvider).Returns(serviceProviderMock.Object);
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
endpointsMock.Object.MapAGUIServer((string)null!, "/api/agent"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MapAGUIServer_WithNullAgentBuilder_ThrowsArgumentNullException()
|
||||
{
|
||||
// Arrange
|
||||
Mock<IEndpointRouteBuilder> endpointsMock = new();
|
||||
Mock<IServiceProvider> serviceProviderMock = new();
|
||||
endpointsMock.Setup(e => e.ServiceProvider).Returns(serviceProviderMock.Object);
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
endpointsMock.Object.MapAGUIServer((IHostedAgentBuilder)null!, "/api/agent"));
|
||||
}
|
||||
|
||||
private sealed class TestAgent : AIAgent
|
||||
{
|
||||
protected override Task<AgentResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) => throw new NotImplementedException();
|
||||
|
||||
protected override IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(IEnumerable<ChatMessage> messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) => throw new NotImplementedException();
|
||||
|
||||
protected override ValueTask<AgentSession> CreateSessionCoreAsync(CancellationToken cancellationToken = default) => throw new NotImplementedException();
|
||||
|
||||
protected override ValueTask<AgentSession> DeserializeSessionCoreAsync(JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => throw new NotImplementedException();
|
||||
|
||||
protected override ValueTask<JsonElement> SerializeSessionCoreAsync(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private sealed class NamedTestAgent : AIAgent
|
||||
{
|
||||
protected override string? IdCore => "named-test-agent";
|
||||
|
||||
public override string? Name => "test-agent";
|
||||
|
||||
protected override Task<AgentResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) => throw new NotImplementedException();
|
||||
|
||||
protected override IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(IEnumerable<ChatMessage> messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) => throw new NotImplementedException();
|
||||
|
||||
protected override ValueTask<AgentSession> CreateSessionCoreAsync(CancellationToken cancellationToken = default) => throw new NotImplementedException();
|
||||
|
||||
protected override ValueTask<AgentSession> DeserializeSessionCoreAsync(JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => throw new NotImplementedException();
|
||||
|
||||
protected override ValueTask<JsonElement> SerializeSessionCoreAsync(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
#if !NET10_0_OR_GREATER
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AGUI.Abstractions;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
|
||||
namespace Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for the <see cref="AGUIServerSentEventsResult"/> class.
|
||||
/// </summary>
|
||||
public sealed class AGUIServerSentEventsResultTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_SetsCorrectResponseHeaders_ContentTypeAndCacheControlAsync()
|
||||
{
|
||||
// Arrange
|
||||
List<BaseEvent> events = [];
|
||||
ILogger<AGUIServerSentEventsResult> logger = NullLogger<AGUIServerSentEventsResult>.Instance;
|
||||
AGUIServerSentEventsResult result = new(events.ToAsyncEnumerableAsync(), logger);
|
||||
DefaultHttpContext httpContext = new();
|
||||
httpContext.Response.Body = new MemoryStream();
|
||||
|
||||
// Act
|
||||
await result.ExecuteAsync(httpContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("text/event-stream", httpContext.Response.ContentType);
|
||||
Assert.Equal("no-cache,no-store", httpContext.Response.Headers.CacheControl.ToString());
|
||||
Assert.Equal("no-cache", httpContext.Response.Headers.Pragma.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_SerializesEventsInSSEFormat_WithDataPrefixAndNewlinesAsync()
|
||||
{
|
||||
// Arrange
|
||||
List<BaseEvent> events =
|
||||
[
|
||||
new RunStartedEvent { ThreadId = "thread1", RunId = "run1" },
|
||||
new RunFinishedEvent { ThreadId = "thread1", RunId = "run1" }
|
||||
];
|
||||
ILogger<AGUIServerSentEventsResult> logger = NullLogger<AGUIServerSentEventsResult>.Instance;
|
||||
AGUIServerSentEventsResult result = new(events.ToAsyncEnumerableAsync(), logger);
|
||||
DefaultHttpContext httpContext = new();
|
||||
MemoryStream responseStream = new();
|
||||
httpContext.Response.Body = responseStream;
|
||||
|
||||
// Act
|
||||
await result.ExecuteAsync(httpContext);
|
||||
|
||||
// Assert
|
||||
string responseContent = Encoding.UTF8.GetString(responseStream.ToArray());
|
||||
Assert.Contains("data: ", responseContent);
|
||||
Assert.Contains("\n\n", responseContent);
|
||||
string[] eventStrings = responseContent.Split("\n\n", StringSplitOptions.RemoveEmptyEntries);
|
||||
Assert.Equal(2, eventStrings.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_FlushesResponse_AfterEachEventAsync()
|
||||
{
|
||||
// Arrange
|
||||
List<BaseEvent> events =
|
||||
[
|
||||
new RunStartedEvent { ThreadId = "thread1", RunId = "run1" },
|
||||
new TextMessageContentEvent { MessageId = "msg1", Delta = "Hello" },
|
||||
new RunFinishedEvent { ThreadId = "thread1", RunId = "run1" }
|
||||
];
|
||||
ILogger<AGUIServerSentEventsResult> logger = NullLogger<AGUIServerSentEventsResult>.Instance;
|
||||
AGUIServerSentEventsResult result = new(events.ToAsyncEnumerableAsync(), logger);
|
||||
DefaultHttpContext httpContext = new();
|
||||
MemoryStream responseStream = new();
|
||||
httpContext.Response.Body = responseStream;
|
||||
|
||||
// Act
|
||||
await result.ExecuteAsync(httpContext);
|
||||
|
||||
// Assert
|
||||
string responseContent = Encoding.UTF8.GetString(responseStream.ToArray());
|
||||
string[] eventStrings = responseContent.Split("\n\n", StringSplitOptions.RemoveEmptyEntries);
|
||||
Assert.Equal(3, eventStrings.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_WithEmptyEventStream_CompletesSuccessfullyAsync()
|
||||
{
|
||||
// Arrange
|
||||
List<BaseEvent> events = [];
|
||||
ILogger<AGUIServerSentEventsResult> logger = NullLogger<AGUIServerSentEventsResult>.Instance;
|
||||
AGUIServerSentEventsResult result = new(events.ToAsyncEnumerableAsync(), logger);
|
||||
DefaultHttpContext httpContext = new();
|
||||
httpContext.Response.Body = new MemoryStream();
|
||||
|
||||
// Act
|
||||
await result.ExecuteAsync(httpContext);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_RespectsCancellationToken_WhenCancelledAsync()
|
||||
{
|
||||
// Arrange
|
||||
using CancellationTokenSource cts = new();
|
||||
List<BaseEvent> events =
|
||||
[
|
||||
new RunStartedEvent { ThreadId = "thread1", RunId = "run1" },
|
||||
new TextMessageContentEvent { MessageId = "msg1", Delta = "Hello" }
|
||||
];
|
||||
|
||||
async IAsyncEnumerable<BaseEvent> GetEventsWithCancellationAsync()
|
||||
{
|
||||
foreach (BaseEvent evt in events)
|
||||
{
|
||||
yield return evt;
|
||||
await Task.Delay(10);
|
||||
}
|
||||
}
|
||||
|
||||
ILogger<AGUIServerSentEventsResult> logger = NullLogger<AGUIServerSentEventsResult>.Instance;
|
||||
AGUIServerSentEventsResult result = new(GetEventsWithCancellationAsync(), logger);
|
||||
DefaultHttpContext httpContext = new();
|
||||
httpContext.Response.Body = new MemoryStream();
|
||||
httpContext.RequestAborted = cts.Token;
|
||||
|
||||
// Act
|
||||
cts.Cancel();
|
||||
|
||||
// Assert
|
||||
await Assert.ThrowsAnyAsync<OperationCanceledException>(() => result.ExecuteAsync(httpContext));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_WithNullHttpContext_ThrowsArgumentNullExceptionAsync()
|
||||
{
|
||||
// Arrange
|
||||
List<BaseEvent> events = [];
|
||||
ILogger<AGUIServerSentEventsResult> logger = NullLogger<AGUIServerSentEventsResult>.Instance;
|
||||
AGUIServerSentEventsResult result = new(events.ToAsyncEnumerableAsync(), logger);
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<ArgumentNullException>(() => result.ExecuteAsync(null!));
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text.Json;
|
||||
using AGUI.Abstractions;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for the JSON options configured by <c>AddAGUIServer</c> (via <see cref="ConfigureAGUIJsonOptions"/>).
|
||||
/// </summary>
|
||||
public sealed class ConfigureAGUIJsonOptionsTests
|
||||
{
|
||||
[Fact]
|
||||
public void AddAGUIServer_ConfiguresJsonOptions_ResolvesAGUIWireTypes()
|
||||
{
|
||||
JsonSerializerOptions options = BuildConfiguredSerializerOptions();
|
||||
|
||||
// The AG-UI wire context must be in the resolver chain (needed on the net10
|
||||
// TypedResults.ServerSentEvents path, which serializes events through these options).
|
||||
options.Invoking(o => o.GetTypeInfo(typeof(RunStartedEvent))).Should().NotThrow();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddAGUIServer_ConfiguresJsonOptions_ResolvesAgentAbstractionsTypes()
|
||||
{
|
||||
JsonSerializerOptions options = BuildConfiguredSerializerOptions();
|
||||
|
||||
// The Agent Framework abstractions resolver must also be present so M.E.AI types resolve.
|
||||
options.Invoking(o => o.GetTypeInfo(typeof(ChatMessage))).Should().NotThrow();
|
||||
}
|
||||
|
||||
private static JsonSerializerOptions BuildConfiguredSerializerOptions()
|
||||
{
|
||||
ServiceCollection services = new();
|
||||
services.AddOptions();
|
||||
services.AddAGUIServer();
|
||||
|
||||
using ServiceProvider provider = services.BuildServiceProvider();
|
||||
return provider
|
||||
.GetRequiredService<IOptions<Microsoft.AspNetCore.Http.Json.JsonOptions>>()
|
||||
.Value.SerializerOptions;
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>$(TargetFrameworksCore)</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible($(TargetFramework), 'net10.0'))">
|
||||
<PackageReference Include="System.Linq.AsyncEnumerable" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.csproj" />
|
||||
<PackageReference Include="AGUI.Abstractions" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
#if !NET10_0_OR_GREATER
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests;
|
||||
|
||||
internal static class TestHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension method to convert a synchronous enumerable to an async enumerable for testing purposes.
|
||||
/// </summary>
|
||||
public static async IAsyncEnumerable<T> ToAsyncEnumerableAsync<T>(this IEnumerable<T> source)
|
||||
{
|
||||
foreach (T item in source)
|
||||
{
|
||||
yield return item;
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user