From 5b4aae3ea317d9347d855b910d4c6922f7a6ec58 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 28 Aug 2026 13:39:41 -0400 Subject: [PATCH 1/7] tests: removes dependency on fluent assersion because of licensing concerns --- dotnet/Directory.Packages.props | 1 - .../BasicStreamingTests.cs | 111 ++++---- .../ForwardedPropertiesTests.cs | 75 +++-- ...ng.AGUI.AspNetCore.IntegrationTests.csproj | 1 - .../SessionPersistenceTests.cs | 29 +- .../SharedStateTests.cs | 53 ++-- .../ToolCallingTests.cs | 140 ++++----- .../ConfigureAGUIJsonOptionsTests.cs | 5 +- ...I.Hosting.AGUI.AspNetCore.UnitTests.csproj | 1 - .../ListAgentToolsWithTasksTests.cs | 23 +- .../McpTaskOptionsTests.cs | 13 +- .../Microsoft.Agents.AI.Mcp.UnitTests.csproj | 1 - .../TaskAwareMcpClientAIFunctionTests.cs | 144 +++++----- .../DefaultMcpToolHandlerTests.cs | 239 ++++++++-------- ...Workflows.Declarative.Mcp.UnitTests.csproj | 1 - .../DefaultHttpRequestHandlerTests.cs | 79 +++--- .../Kit/PortableValuePredicateTests.cs | 29 +- ....AI.Workflows.Declarative.UnitTests.csproj | 1 - .../ExecutorRouteGeneratorTests.cs | 211 +++++++------- .../GeneratorTestHelper.cs | 12 +- ...s.AI.Workflows.Generators.UnitTests.csproj | 1 - .../SyntaxTreeFluentExtensions.cs | 173 +++--------- .../AIAgentHostExecutorTests.cs | 68 ++--- .../AIAgentHostingExecutorTestsBase.cs | 29 +- .../AgentEventsTests.cs | 13 +- .../AgentWorkflowBuilderTests.cs | 20 +- .../AggregatingExecutorTests.cs | 17 +- .../JsonCheckpointSerializationTests.cs | 42 +-- .../ChatForwardingExecutorTests.cs | 24 +- .../ChatProtocolExecutorTests.cs | 69 +++-- .../CheckpointManagerLatestTests.cs | 5 +- .../CheckpointParentTests.cs | 25 +- .../CheckpointResumeTests.cs | 125 +++----- .../CheckpointVersionToleranceTests.cs | 9 +- .../ConcurrentWorkflowBuilderTests.cs | 27 +- .../DynamicRequestPortTests.cs | 18 +- .../EdgeMapSmokeTests.cs | 25 +- .../EdgeRunnerTests.cs | 19 +- .../ExternalResponsePortCorrelationTests.cs | 25 +- .../FileSystemJsonCheckpointStoreTests.cs | 51 ++-- .../FunctionExecutorTests.cs | 69 +++-- ...tResponseOutputFilteringAndTaggingTests.cs | 73 +++-- .../GroupChatOrchestrationTests.cs | 109 +++---- .../GroupChatWorkflowBuilderTests.cs | 18 +- .../HandoffAgentExecutorTests.cs | 31 +- .../HandoffMessageFilterTests.cs | 28 +- .../HandoffOrchestrationTests.cs | 43 ++- .../HandoffWorkflowBuilderTests.cs | 18 +- .../InProcessExecutionTests.cs | 36 ++- .../InProcessExecutorEventsTests.cs | 55 ++-- .../InProcessStateTests.cs | 27 +- .../InputWaiterTests.cs | 21 +- .../JsonSerializationTests.cs | 224 ++++++++------- .../MagenticManagerTests.cs | 36 +-- .../MagenticOrchestrationTests.cs | 266 ++++++++---------- .../MagenticOrchestratorTests.cs | 9 +- .../MagenticProgressLedgerTests.cs | 53 ++-- .../MagenticWorkflowBuilderTests.cs | 26 +- .../MessageDeliveryValidation.cs | 19 +- .../MessageMergerTests.cs | 118 ++++---- ...osoft.Agents.AI.Workflows.UnitTests.csproj | 1 - .../ObservabilityTests.cs | 148 ++++------ .../OutputFilterTests.cs | 25 +- .../OutputTagTests.cs | 33 ++- .../PolymorphicOutputTests.cs | 35 ++- .../PortableValueTests.cs | 47 ++-- .../PromptTemplatesTests.cs | 92 +++--- .../RepresentationTests.cs | 9 +- .../RoundRobinGroupChatManagerTests.cs | 38 ++- .../RouteBuilderTests.cs | 136 +++++---- .../05_Simple_Workflow_Checkpointing.cs | 11 +- .../Sample/08_Subworkflow_Simple.cs | 7 +- .../Sample/09_Subworkflow_ExternalRequest.cs | 23 +- .../SampleSmokeTest.cs | 19 +- .../SequentialWorkflowBuilderTests.cs | 50 ++-- .../SpecializedExecutorSmokeTests.cs | 25 +- .../StateKeyObjectTests.cs | 9 +- .../StateManagerTests.cs | 158 +++++------ .../StreamingAggregatorsTests.cs | 44 ++- .../TestProgressLedgerState.cs | 23 +- .../TestRequestAgent.cs | 18 +- .../TypeIdVersionToleranceTests.cs | 55 ++-- .../WorkflowAgentCheckpointIdentityTests.cs | 21 +- .../WorkflowBuilderTests.cs | 196 +++++++------ .../WorkflowHostSmokeTests.cs | 195 ++++++------- .../WorkflowRunActivityStopTests.cs | 54 ++-- .../WorkflowSessionTests.cs | 23 +- .../WorkflowVisualizerTests.cs | 182 ++++++------ 88 files changed, 2272 insertions(+), 2638 deletions(-) diff --git a/dotnet/Directory.Packages.props b/dotnet/Directory.Packages.props index 9b95114b167..a33213fdfee 100644 --- a/dotnet/Directory.Packages.props +++ b/dotnet/Directory.Packages.props @@ -144,7 +144,6 @@ - diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/BasicStreamingTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/BasicStreamingTests.cs index 97addd3d885..76a23fb60cb 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/BasicStreamingTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/BasicStreamingTests.cs @@ -12,7 +12,6 @@ using System.Threading.Tasks; using AGUI.Abstractions; using AGUI.Client; -using FluentAssertions; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.TestHost; @@ -45,16 +44,16 @@ public async Task ClientReceivesStreamedAssistantMessageAsync() } // Assert - session.Should().NotBeNull(); + Assert.NotNull(session); - updates.Should().NotBeEmpty(); - updates.Should().AllSatisfy(u => u.Role.Should().Be(ChatRole.Assistant)); + Assert.NotEmpty(updates); + Assert.All(updates, u => Assert.Equal(ChatRole.Assistant, u.Role)); // Verify assistant response message AgentResponse response = updates.ToAgentResponse(); - response.Messages.Should().HaveCount(1); - response.Messages[0].Role.Should().Be(ChatRole.Assistant); - response.Messages[0].Text.Should().Be("Hello from fake agent!"); + ChatMessage responseMessage = Assert.Single(response.Messages); + Assert.Equal(ChatRole.Assistant, responseMessage.Role); + Assert.Equal("Hello from fake agent!", responseMessage.Text); } [Fact] @@ -76,39 +75,39 @@ public async Task ClientReceivesRunLifecycleEventsAsync() } // Assert - RunStarted should be the first update - updates.Should().NotBeEmpty(); - updates[0].ResponseId.Should().NotBeNullOrEmpty(); + Assert.NotEmpty(updates); + Assert.False(string.IsNullOrEmpty(updates[0].ResponseId)); ChatResponseUpdate firstUpdate = updates[0].AsChatResponseUpdate(); // The AG-UI thread id is surfaced on the RUN_STARTED event (the new AGUI.Client keeps the // client stateless and never populates ChatResponseUpdate.ConversationId). string? threadId = (firstUpdate.RawRepresentation as RunStartedEvent)?.ThreadId; string? runId = updates[0].ResponseId; - threadId.Should().NotBeNullOrEmpty(); - runId.Should().NotBeNullOrEmpty(); + Assert.False(string.IsNullOrEmpty(threadId)); + Assert.False(string.IsNullOrEmpty(runId)); // Should have received text updates - updates.Should().Contain(u => !string.IsNullOrEmpty(u.Text)); + Assert.Contains(updates, u => !string.IsNullOrEmpty(u.Text)); // All text content updates should have the same message ID List textUpdates = updates.Where(u => !string.IsNullOrEmpty(u.Text)).ToList(); - textUpdates.Should().NotBeEmpty(); + Assert.NotEmpty(textUpdates); string? firstMessageId = textUpdates.FirstOrDefault()?.MessageId; - firstMessageId.Should().NotBeNullOrEmpty(); - textUpdates.Should().AllSatisfy(u => u.MessageId.Should().Be(firstMessageId)); + Assert.False(string.IsNullOrEmpty(firstMessageId)); + Assert.All(textUpdates, u => Assert.Equal(firstMessageId, u.MessageId)); // RunFinished should be the last update AgentResponseUpdate lastUpdate = updates[^1]; - lastUpdate.ResponseId.Should().Be(runId); + Assert.Equal(runId, lastUpdate.ResponseId); ChatResponseUpdate lastChatUpdate = lastUpdate.AsChatResponseUpdate(); // The stateless client never populates ChatResponseUpdate.ConversationId; thread identity stays // on the AG-UI wire events instead, so verify the RUN_FINISHED event carries the same ids. - lastChatUpdate.ConversationId.Should().BeNull(); + Assert.Null(lastChatUpdate.ConversationId); RunFinishedEvent? runFinished = updates .Select(u => u.AsChatResponseUpdate().RawRepresentation as RunFinishedEvent) .FirstOrDefault(e => e is not null); - runFinished.Should().NotBeNull(); - runFinished!.ThreadId.Should().Be(threadId); - runFinished.RunId.Should().Be(runId); + Assert.NotNull(runFinished); + Assert.Equal(threadId, runFinished!.ThreadId); + Assert.Equal(runId, runFinished.RunId); } [Fact] @@ -125,9 +124,9 @@ public async Task RunAsyncAggregatesStreamingUpdatesAsync() AgentResponse response = await agent.RunAsync([userMessage], session, new AgentRunOptions(), CancellationToken.None); // Assert - response.Messages.Should().NotBeEmpty(); - response.Messages.Should().Contain(m => m.Role == ChatRole.Assistant); - response.Messages.Should().Contain(m => m.Text == "Hello from fake agent!"); + Assert.NotEmpty(response.Messages); + Assert.Contains(response.Messages, m => m.Role == ChatRole.Assistant); + Assert.Contains(response.Messages, m => m.Text == "Hello from fake agent!"); } [Fact] @@ -148,9 +147,9 @@ public async Task AGUIChatClientBackedAgentUsesLocalChatHistoryAcrossTurnsAsync( } // Assert first turn completed - firstTurnUpdates.Should().Contain(u => !string.IsNullOrEmpty(u.Text)); - firstTurnUpdates.Should().AllSatisfy(u => u.AsChatResponseUpdate().ConversationId.Should().BeNull()); - chatClientSession.ConversationId.Should().BeNull(); + Assert.Contains(firstTurnUpdates, u => !string.IsNullOrEmpty(u.Text)); + Assert.All(firstTurnUpdates, u => Assert.Null(u.AsChatResponseUpdate().ConversationId)); + Assert.Null(chatClientSession.ConversationId); // Act - Second turn with another message ChatMessage secondUserMessage = new(ChatRole.User, "Second question"); @@ -161,34 +160,34 @@ public async Task AGUIChatClientBackedAgentUsesLocalChatHistoryAcrossTurnsAsync( } // Assert second turn completed - secondTurnUpdates.Should().Contain(u => !string.IsNullOrEmpty(u.Text)); - secondTurnUpdates.Should().AllSatisfy(u => u.AsChatResponseUpdate().ConversationId.Should().BeNull()); - chatClientSession.ConversationId.Should().BeNull(); + Assert.Contains(secondTurnUpdates, u => !string.IsNullOrEmpty(u.Text)); + Assert.All(secondTurnUpdates, u => Assert.Null(u.AsChatResponseUpdate().ConversationId)); + Assert.Null(chatClientSession.ConversationId); // Verify the local provider retained both turns. - InMemoryChatHistoryProvider historyProvider = agent.ChatHistoryProvider.Should().BeOfType().Subject; + InMemoryChatHistoryProvider historyProvider = Assert.IsType(agent.ChatHistoryProvider); List history = historyProvider.GetMessages(chatClientSession); - history.Should().HaveCount(4); - history[0].Role.Should().Be(ChatRole.User); - history[0].Text.Should().Be("First question"); - history[1].Role.Should().Be(ChatRole.Assistant); - history[1].Text.Should().Be("Hello from fake agent!"); - history[2].Role.Should().Be(ChatRole.User); - history[2].Text.Should().Be("Second question"); - history[3].Role.Should().Be(ChatRole.Assistant); - history[3].Text.Should().Be("Hello from fake agent!"); + Assert.Equal(4, history.Count); + Assert.Equal(ChatRole.User, history[0].Role); + Assert.Equal("First question", history[0].Text); + Assert.Equal(ChatRole.Assistant, history[1].Role); + Assert.Equal("Hello from fake agent!", history[1].Text); + Assert.Equal(ChatRole.User, history[2].Role); + Assert.Equal("Second question", history[2].Text); + Assert.Equal(ChatRole.Assistant, history[3].Role); + Assert.Equal("Hello from fake agent!", history[3].Text); // Verify first turn assistant response. AgentResponse firstResponse = firstTurnUpdates.ToAgentResponse(); - firstResponse.Messages.Should().HaveCount(1); - firstResponse.Messages[0].Role.Should().Be(ChatRole.Assistant); - firstResponse.Messages[0].Text.Should().Be("Hello from fake agent!"); + ChatMessage firstResponseMessage = Assert.Single(firstResponse.Messages); + Assert.Equal(ChatRole.Assistant, firstResponseMessage.Role); + Assert.Equal("Hello from fake agent!", firstResponseMessage.Text); // Verify second turn assistant response. AgentResponse secondResponse = secondTurnUpdates.ToAgentResponse(); - secondResponse.Messages.Should().HaveCount(1); - secondResponse.Messages[0].Role.Should().Be(ChatRole.Assistant); - secondResponse.Messages[0].Text.Should().Be("Hello from fake agent!"); + ChatMessage secondResponseMessage = Assert.Single(secondResponse.Messages); + Assert.Equal(ChatRole.Assistant, secondResponseMessage.Role); + Assert.Equal("Hello from fake agent!", secondResponseMessage.Text); } [Fact] @@ -211,16 +210,16 @@ public async Task AgentSendsMultipleMessagesInOneTurnAsync() // Assert - Should have received text updates with different message IDs List textUpdates = updates.Where(u => !string.IsNullOrEmpty(u.Text)).ToList(); - textUpdates.Should().NotBeEmpty(); + Assert.NotEmpty(textUpdates); // Extract unique message IDs List messageIds = textUpdates.Select(u => u.MessageId).Where(id => !string.IsNullOrEmpty(id)).Distinct().ToList()!; - messageIds.Should().HaveCountGreaterThan(1, "agent should send multiple messages"); + Assert.True(messageIds.Count > 1); // Verify assistant messages from updates AgentResponse response = updates.ToAgentResponse(); - response.Messages.Should().HaveCountGreaterThan(1); - response.Messages.Should().AllSatisfy(m => m.Role.Should().Be(ChatRole.Assistant)); + Assert.True(response.Messages.Count > 1); + Assert.All(response.Messages, m => Assert.Equal(ChatRole.Assistant, m.Role)); } [Fact] @@ -249,14 +248,14 @@ public async Task UserSendsMultipleMessagesAtOnceAsync() } // Assert - Should have received assistant response - updates.Should().Contain(u => !string.IsNullOrEmpty(u.Text)); - updates.Should().Contain(u => u.Role == ChatRole.Assistant); + Assert.Contains(updates, u => !string.IsNullOrEmpty(u.Text)); + Assert.Contains(updates, u => u.Role == ChatRole.Assistant); // Verify assistant response message AgentResponse response = updates.ToAgentResponse(); - response.Messages.Should().HaveCount(1); - response.Messages[0].Role.Should().Be(ChatRole.Assistant); - response.Messages[0].Text.Should().Be("Hello from fake agent!"); + ChatMessage responseMessage = Assert.Single(response.Messages); + Assert.Equal(ChatRole.Assistant, responseMessage.Role); + Assert.Equal("Hello from fake agent!", responseMessage.Text); } [Fact] @@ -276,8 +275,8 @@ public async Task PostMalformedOrEmptyBody_ReturnsBadRequestAsync() using HttpResponseMessage emptyResponse = await this._client!.PostAsync(endpoint, empty); // Assert - the hosting glue rejects both with 400 rather than 5xx. - malformedResponse.StatusCode.Should().Be(System.Net.HttpStatusCode.BadRequest); - emptyResponse.StatusCode.Should().Be(System.Net.HttpStatusCode.BadRequest); + Assert.Equal(System.Net.HttpStatusCode.BadRequest, malformedResponse.StatusCode); + Assert.Equal(System.Net.HttpStatusCode.BadRequest, emptyResponse.StatusCode); } private async Task SetupTestServerAsync(bool useMultiMessageAgent = false) diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/ForwardedPropertiesTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/ForwardedPropertiesTests.cs index c3582459d53..dcf9cdbf4ba 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/ForwardedPropertiesTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/ForwardedPropertiesTests.cs @@ -15,7 +15,6 @@ using AGUI.Abstractions; using AGUI.Client; using AGUI.Server; -using FluentAssertions; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.TestHost; @@ -54,10 +53,10 @@ [new ChatMessage(ChatRole.User, "test client forwarding")], } // Assert - fakeAgent.ReceivedContext.Should().ContainSingle(); - fakeAgent.ReceivedContext![0].Description.Should().Be("Current user"); - fakeAgent.ReceivedContext[0].Value.Should().Be("Ada Lovelace"); - fakeAgent.ReceivedForwardedProperties.GetProperty("tenantId").GetString().Should().Be("tenant-123"); + Assert.Single(fakeAgent.ReceivedContext ?? []); + Assert.Equal("Current user", fakeAgent.ReceivedContext![0].Description); + Assert.Equal("Ada Lovelace", fakeAgent.ReceivedContext[0].Value); + Assert.Equal("tenant-123", fakeAgent.ReceivedForwardedProperties.GetProperty("tenantId").GetString()); } [Fact] @@ -83,10 +82,10 @@ public async Task ForwardedProps_AreParsedAndPassedToAgent_WhenProvidedInRequest HttpResponseMessage response = await this._client!.PostAsync(new Uri("/agent", UriKind.Relative), content); // Assert - response.IsSuccessStatusCode.Should().BeTrue(); - fakeAgent.ReceivedForwardedProperties.ValueKind.Should().Be(JsonValueKind.Object); - fakeAgent.ReceivedForwardedProperties.GetProperty("customProp").GetString().Should().Be("customValue"); - fakeAgent.ReceivedForwardedProperties.GetProperty("sessionId").GetString().Should().Be("test-session-123"); + Assert.True(response.IsSuccessStatusCode); + Assert.Equal(JsonValueKind.Object, fakeAgent.ReceivedForwardedProperties.ValueKind); + Assert.Equal("customValue", fakeAgent.ReceivedForwardedProperties.GetProperty("customProp").GetString()); + Assert.Equal("test-session-123", fakeAgent.ReceivedForwardedProperties.GetProperty("sessionId").GetString()); } [Fact] @@ -114,16 +113,16 @@ public async Task ForwardedProps_WithNestedObjects_AreCorrectlyParsedAsync() HttpResponseMessage response = await this._client!.PostAsync(new Uri("/agent", UriKind.Relative), content); // Assert - response.IsSuccessStatusCode.Should().BeTrue(); - fakeAgent.ReceivedForwardedProperties.ValueKind.Should().Be(JsonValueKind.Object); + Assert.True(response.IsSuccessStatusCode); + Assert.Equal(JsonValueKind.Object, fakeAgent.ReceivedForwardedProperties.ValueKind); JsonElement user = fakeAgent.ReceivedForwardedProperties.GetProperty("user"); - user.GetProperty("id").GetString().Should().Be("user-1"); - user.GetProperty("name").GetString().Should().Be("Test User"); + Assert.Equal("user-1", user.GetProperty("id").GetString()); + Assert.Equal("Test User", user.GetProperty("name").GetString()); JsonElement metadata = fakeAgent.ReceivedForwardedProperties.GetProperty("metadata"); - metadata.GetProperty("version").GetString().Should().Be("1.0"); - metadata.GetProperty("feature").GetString().Should().Be("test"); + Assert.Equal("1.0", metadata.GetProperty("version").GetString()); + Assert.Equal("test", metadata.GetProperty("feature").GetString()); } [Fact] @@ -151,16 +150,16 @@ public async Task ForwardedProps_WithArrays_AreCorrectlyParsedAsync() HttpResponseMessage response = await this._client!.PostAsync(new Uri("/agent", UriKind.Relative), content); // Assert - response.IsSuccessStatusCode.Should().BeTrue(); - fakeAgent.ReceivedForwardedProperties.ValueKind.Should().Be(JsonValueKind.Object); + Assert.True(response.IsSuccessStatusCode); + Assert.Equal(JsonValueKind.Object, fakeAgent.ReceivedForwardedProperties.ValueKind); JsonElement tags = fakeAgent.ReceivedForwardedProperties.GetProperty("tags"); - tags.GetArrayLength().Should().Be(3); - tags[0].GetString().Should().Be("tag1"); + Assert.Equal(3, tags.GetArrayLength()); + Assert.Equal("tag1", tags[0].GetString()); JsonElement scores = fakeAgent.ReceivedForwardedProperties.GetProperty("scores"); - scores.GetArrayLength().Should().Be(5); - scores[2].GetInt32().Should().Be(3); + Assert.Equal(5, scores.GetArrayLength()); + Assert.Equal(3, scores[2].GetInt32()); } [Fact] @@ -185,7 +184,7 @@ public async Task ForwardedProps_WhenEmpty_DoesNotCauseErrorsAsync() HttpResponseMessage response = await this._client!.PostAsync(new Uri("/agent", UriKind.Relative), content); // Assert - response.IsSuccessStatusCode.Should().BeTrue(); + Assert.True(response.IsSuccessStatusCode); } [Fact] @@ -209,8 +208,8 @@ public async Task ForwardedProps_WhenNotProvided_AgentStillWorksAsync() HttpResponseMessage response = await this._client!.PostAsync(new Uri("/agent", UriKind.Relative), content); // Assert - response.IsSuccessStatusCode.Should().BeTrue(); - fakeAgent.ReceivedForwardedProperties.ValueKind.Should().Be(JsonValueKind.Undefined); + Assert.True(response.IsSuccessStatusCode); + Assert.Equal(JsonValueKind.Undefined, fakeAgent.ReceivedForwardedProperties.ValueKind); } [Fact] @@ -243,20 +242,20 @@ public async Task ForwardedProps_ReturnsValidSSEResponse_WithTextDeltaEventsAsyn } // Assert - events.Should().NotBeEmpty(); + Assert.NotEmpty(events); // SSE events have EventType = "message" and the actual type is in the JSON data // Should have run_started event - events.Should().Contain(e => e.Data != null && e.Data.Contains("\"type\":\"RUN_STARTED\"")); + Assert.Contains(events, e => e.Data?.Contains("\"type\":\"RUN_STARTED\"") == true); // Should have text_message_start event - events.Should().Contain(e => e.Data != null && e.Data.Contains("\"type\":\"TEXT_MESSAGE_START\"")); + Assert.Contains(events, e => e.Data?.Contains("\"type\":\"TEXT_MESSAGE_START\"") == true); // Should have text_message_content event with the response text - events.Should().Contain(e => e.Data != null && e.Data.Contains("\"type\":\"TEXT_MESSAGE_CONTENT\"")); + Assert.Contains(events, e => e.Data?.Contains("\"type\":\"TEXT_MESSAGE_CONTENT\"") == true); // Should have run_finished event - events.Should().Contain(e => e.Data != null && e.Data.Contains("\"type\":\"RUN_FINISHED\"")); + Assert.Contains(events, e => e.Data?.Contains("\"type\":\"RUN_FINISHED\"") == true); } [Fact] @@ -288,15 +287,15 @@ public async Task ForwardedProps_WithMixedTypes_AreCorrectlyParsedAsync() HttpResponseMessage response = await this._client!.PostAsync(new Uri("/agent", UriKind.Relative), content); // Assert - response.IsSuccessStatusCode.Should().BeTrue(); - fakeAgent.ReceivedForwardedProperties.ValueKind.Should().Be(JsonValueKind.Object); - - fakeAgent.ReceivedForwardedProperties.GetProperty("stringProp").GetString().Should().Be("text"); - fakeAgent.ReceivedForwardedProperties.GetProperty("numberProp").GetInt32().Should().Be(42); - fakeAgent.ReceivedForwardedProperties.GetProperty("boolProp").GetBoolean().Should().BeTrue(); - fakeAgent.ReceivedForwardedProperties.GetProperty("nullProp").ValueKind.Should().Be(JsonValueKind.Null); - fakeAgent.ReceivedForwardedProperties.GetProperty("arrayProp").GetArrayLength().Should().Be(3); - fakeAgent.ReceivedForwardedProperties.GetProperty("objectProp").GetProperty("nested").GetString().Should().Be("value"); + Assert.True(response.IsSuccessStatusCode); + Assert.Equal(JsonValueKind.Object, fakeAgent.ReceivedForwardedProperties.ValueKind); + + Assert.Equal("text", fakeAgent.ReceivedForwardedProperties.GetProperty("stringProp").GetString()); + Assert.Equal(42, fakeAgent.ReceivedForwardedProperties.GetProperty("numberProp").GetInt32()); + Assert.True(fakeAgent.ReceivedForwardedProperties.GetProperty("boolProp").GetBoolean()); + Assert.Equal(JsonValueKind.Null, fakeAgent.ReceivedForwardedProperties.GetProperty("nullProp").ValueKind); + Assert.Equal(3, fakeAgent.ReceivedForwardedProperties.GetProperty("arrayProp").GetArrayLength()); + Assert.Equal("value", fakeAgent.ReceivedForwardedProperties.GetProperty("objectProp").GetProperty("nested").GetString()); } private async Task SetupTestServerAsync(FakeForwardedPropsAgent fakeAgent) diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests.csproj b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests.csproj index 019f0eb7a16..a65db7c2cf5 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests.csproj +++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests.csproj @@ -11,7 +11,6 @@ - diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/SessionPersistenceTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/SessionPersistenceTests.cs index 3c9c3a0281a..576f6c2d1c3 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/SessionPersistenceTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/SessionPersistenceTests.cs @@ -12,7 +12,6 @@ using System.Threading.Tasks; using AGUI.Abstractions; using AGUI.Client; -using FluentAssertions; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.TestHost; @@ -53,11 +52,11 @@ public async Task MultiTurnWithSessionStore_PersistsSessionAcrossRequestsAsync() RunStartedEvent? firstRunStarted = firstTurnUpdates .Select(u => u.AsChatResponseUpdate().RawRepresentation as RunStartedEvent) .FirstOrDefault(e => e is not null); - firstRunStarted.Should().NotBeNull(); + Assert.NotNull(firstRunStarted); string threadId = firstRunStarted!.ThreadId; string previousRunId = firstRunStarted.RunId; - threadId.Should().NotBeNullOrEmpty(); - previousRunId.Should().NotBeNullOrEmpty(); + Assert.False(string.IsNullOrEmpty(threadId)); + Assert.False(string.IsNullOrEmpty(previousRunId)); ChatMessage secondUserMessage = new(ChatRole.User, "Second message"); var continuationOptions = new ChatClientAgentRunOptions @@ -82,14 +81,14 @@ public async Task MultiTurnWithSessionStore_PersistsSessionAcrossRequestsAsync() // If session persistence were broken, both turns would return "Turn 1" // because a fresh session (with turn count 0) would be created each time. AgentResponse firstResponse = firstTurnUpdates.ToAgentResponse(); - firstResponse.Messages.Should().HaveCount(1); - firstResponse.Messages[0].Role.Should().Be(ChatRole.Assistant); - firstResponse.Messages[0].Text.Should().Contain("Turn 1:"); + ChatMessage firstResponseMessage = Assert.Single(firstResponse.Messages); + Assert.Equal(ChatRole.Assistant, firstResponseMessage.Role); + Assert.Contains("Turn 1:", firstResponseMessage.Text); AgentResponse secondResponse = secondTurnUpdates.ToAgentResponse(); - secondResponse.Messages.Should().HaveCount(1); - secondResponse.Messages[0].Role.Should().Be(ChatRole.Assistant); - secondResponse.Messages[0].Text.Should().Contain("Turn 2:"); + ChatMessage secondResponseMessage = Assert.Single(secondResponse.Messages); + Assert.Equal(ChatRole.Assistant, secondResponseMessage.Role); + Assert.Contains("Turn 2:", secondResponseMessage.Text); } [Fact] @@ -111,13 +110,13 @@ public async Task MapAGUIServer_WithAgentName_StreamsResponseCorrectlyAsync() } // Assert - updates.Should().NotBeEmpty(); - updates.Should().AllSatisfy(u => u.Role.Should().Be(ChatRole.Assistant)); + Assert.NotEmpty(updates); + Assert.All(updates, u => Assert.Equal(ChatRole.Assistant, u.Role)); AgentResponse response = updates.ToAgentResponse(); - response.Messages.Should().HaveCount(1); - response.Messages[0].Role.Should().Be(ChatRole.Assistant); - response.Messages[0].Text.Should().Be("Turn 1: Hello from session agent!"); + ChatMessage responseMessage = Assert.Single(response.Messages); + Assert.Equal(ChatRole.Assistant, responseMessage.Role); + Assert.Equal("Turn 1: Hello from session agent!", responseMessage.Text); } private async Task SetupTestServerWithSessionStoreAsync() diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/SharedStateTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/SharedStateTests.cs index 802dbaa651b..d0244f7bf03 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/SharedStateTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/SharedStateTests.cs @@ -13,7 +13,6 @@ using AGUI.Abstractions; using AGUI.Client; using AGUI.Server; -using FluentAssertions; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.TestHost; @@ -51,12 +50,12 @@ public async Task StateSnapshot_IsSurfacedAsRawStateSnapshotEventAsync() } // Assert - the state snapshot is surfaced as a StateSnapshotEvent raw representation. - updates.Should().NotBeEmpty(); + Assert.NotEmpty(updates); StateSnapshotEvent? snapshot = FindStateSnapshot(updates); - snapshot.Should().NotBeNull("should receive a STATE_SNAPSHOT event"); - snapshot!.Snapshot.GetProperty("counter").GetInt32().Should().Be(43, "state should be incremented"); - snapshot.Snapshot.GetProperty("status").GetString().Should().Be("active"); + Assert.NotNull(snapshot); + Assert.Equal(43, snapshot!.Snapshot.GetProperty("counter").GetInt32()); + Assert.Equal("active", snapshot.Snapshot.GetProperty("status").GetString()); } [Fact] @@ -83,12 +82,12 @@ public async Task StateSnapshot_UpdateHasAssistantRoleAndNoConversationIdAsync() // ConversationId unset (state identity stays on the AG-UI wire events). AgentResponseUpdate? stateUpdate = updates .FirstOrDefault(u => u.AsChatResponseUpdate().RawRepresentation is StateSnapshotEvent); - stateUpdate.Should().NotBeNull(); + Assert.NotNull(stateUpdate); ChatResponseUpdate chatUpdate = stateUpdate!.AsChatResponseUpdate(); - chatUpdate.RawRepresentation.Should().BeOfType(); - chatUpdate.ConversationId.Should().BeNull(); - chatUpdate.Role.Should().Be(ChatRole.Assistant); + Assert.True((chatUpdate.RawRepresentation) is StateSnapshotEvent); + Assert.Null(chatUpdate.ConversationId); + Assert.Equal(ChatRole.Assistant, chatUpdate.Role); } [Fact] @@ -114,13 +113,13 @@ public async Task ComplexState_WithNestedObjectsAndArrays_RoundTripsCorrectlyAsy // Assert StateSnapshotEvent? snapshot = FindStateSnapshot(updates); - snapshot.Should().NotBeNull(); + Assert.NotNull(snapshot); JsonElement receivedState = snapshot!.Snapshot; - receivedState.GetProperty("sessionId").GetString().Should().Be("test-123"); - receivedState.GetProperty("nested").GetProperty("count").GetInt32().Should().Be(10); - receivedState.GetProperty("array").GetArrayLength().Should().Be(3); - receivedState.GetProperty("tags").GetArrayLength().Should().Be(2); + Assert.Equal("test-123", receivedState.GetProperty("sessionId").GetString()); + Assert.Equal(10, receivedState.GetProperty("nested").GetProperty("count").GetInt32()); + Assert.Equal(3, receivedState.GetProperty("array").GetArrayLength()); + Assert.Equal(2, receivedState.GetProperty("tags").GetArrayLength()); } [Fact] @@ -145,8 +144,8 @@ public async Task StateSnapshot_CanBeUsedInSubsequentRequest_ForStateRoundTripAs // Feed the returned state snapshot back into the second round. StateSnapshotEvent? firstSnapshot = FindStateSnapshot(firstRoundUpdates); - firstSnapshot.Should().NotBeNull(); - firstSnapshot!.Snapshot.GetProperty("counter").GetInt32().Should().Be(2); + Assert.NotNull(firstSnapshot); + Assert.Equal(2, firstSnapshot!.Snapshot.GetProperty("counter").GetInt32()); ChatMessage secondUserMessage = new(ChatRole.User, "increment again"); @@ -158,8 +157,8 @@ public async Task StateSnapshot_CanBeUsedInSubsequentRequest_ForStateRoundTripAs // Assert - Second round should have incremented counter again. StateSnapshotEvent? secondSnapshot = FindStateSnapshot(secondRoundUpdates); - secondSnapshot.Should().NotBeNull(); - secondSnapshot!.Snapshot.GetProperty("counter").GetInt32().Should().Be(3, "counter should be incremented twice: 1 -> 2 -> 3"); + Assert.NotNull(secondSnapshot); + Assert.Equal(3, secondSnapshot!.Snapshot.GetProperty("counter").GetInt32()); } [Fact] @@ -182,9 +181,9 @@ public async Task WithoutState_AgentBehavesNormally_NoStateSnapshotReturnedAsync } // Assert - updates.Should().NotBeEmpty(); - FindStateSnapshot(updates).Should().BeNull("should not return state snapshot when no state is provided"); - updates.Should().Contain(u => u.Contents.Any(c => c is TextContent)); + Assert.NotEmpty(updates); + Assert.Null(FindStateSnapshot(updates)); + Assert.Contains(updates, u => u.Contents.Any(c => c is TextContent)); } [Fact] @@ -208,9 +207,9 @@ public async Task EmptyState_DoesNotTriggerStateHandlingAsync() } // Assert - empty state {} should be treated as no state. - updates.Should().NotBeEmpty(); - FindStateSnapshot(updates).Should().BeNull("empty state should be treated as no state"); - updates.Should().Contain(u => u.Contents.Any(c => c is TextContent)); + Assert.NotEmpty(updates); + Assert.Null(FindStateSnapshot(updates)); + Assert.Contains(updates, u => u.Contents.Any(c => c is TextContent)); } [Fact] @@ -232,9 +231,9 @@ public async Task NonStreamingRunAsync_WithState_ReturnsTextResponseAsync() // content-less STATE_SNAPSHOT update (Microsoft.Extensions.AI only materializes updates that carry // content), so the non-streaming path surfaces the aggregated text response. The state round-trip // itself is verified by the streaming tests above. - response.Should().NotBeNull(); - response.Messages.Should().NotBeEmpty(); - response.Text.Should().Contain("State processed"); + Assert.NotNull(response); + Assert.NotEmpty(response.Messages); + Assert.Contains("State processed", response.Text); } private ChatClientAgent CreateAgent() diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/ToolCallingTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/ToolCallingTests.cs index 948a4bb9a36..b19ed3b9c92 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/ToolCallingTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/ToolCallingTests.cs @@ -10,7 +10,6 @@ using System.Threading; using System.Threading.Tasks; using AGUI.Client; -using FluentAssertions; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.TestHost; @@ -56,18 +55,18 @@ public async Task ServerTriggersSingleFunctionCallAsync() } // Assert - callCount.Should().Be(1, "server function should be called once"); - updates.Should().Contain(u => u.Contents.Any(c => c is FunctionCallContent), "should contain function call"); - updates.Should().Contain(u => u.Contents.Any(c => c is FunctionResultContent), "should contain function result"); + Assert.Equal(1, callCount); + Assert.Contains(updates, u => u.Contents.Any(c => c is FunctionCallContent)); + Assert.Contains(updates, u => u.Contents.Any(c => c is FunctionResultContent)); var functionCallUpdates = updates.Where(u => u.Contents.Any(c => c is FunctionCallContent)).ToList(); - functionCallUpdates.Should().HaveCount(1); + Assert.Single(functionCallUpdates ?? []); var functionResultUpdates = updates.Where(u => u.Contents.Any(c => c is FunctionResultContent)).ToList(); - functionResultUpdates.Should().HaveCount(1); + Assert.Single(functionResultUpdates ?? []); - var resultContent = functionResultUpdates[0].Contents.OfType().First(); - resultContent.Result.Should().NotBeNull(); + FunctionResultContent resultContent = Assert.Single(updates.SelectMany(u => u.Contents.OfType())); + Assert.NotNull(resultContent.Result); } [Fact] @@ -104,19 +103,19 @@ public async Task ServerTriggersMultipleFunctionCallsAsync() } // Assert - getWeatherCallCount.Should().Be(1, "GetWeather should be called once"); - getTimeCallCount.Should().Be(1, "GetTime should be called once"); + Assert.Equal(1, getWeatherCallCount); + Assert.Equal(1, getTimeCallCount); var functionCallUpdates = updates.Where(u => u.Contents.Any(c => c is FunctionCallContent)).ToList(); - functionCallUpdates.Should().NotBeEmpty("should contain function calls"); + Assert.NotEmpty(functionCallUpdates); var functionCalls = updates.SelectMany(u => u.Contents.OfType()).ToList(); - functionCalls.Should().HaveCount(2, "should have 2 function calls"); - functionCalls.Should().Contain(fc => fc.Name == "GetWeather"); - functionCalls.Should().Contain(fc => fc.Name == "GetTime"); + Assert.Equal(2, functionCalls.Count); + Assert.Contains(functionCalls, fc => fc.Name == "GetWeather"); + Assert.Contains(functionCalls, fc => fc.Name == "GetTime"); var functionResults = updates.SelectMany(u => u.Contents.OfType()).ToList(); - functionResults.Should().HaveCount(2, "should have 2 function results"); + Assert.Equal(2, functionResults.Count); } [Fact] @@ -145,18 +144,18 @@ public async Task ClientTriggersSingleFunctionCallAsync() } // Assert - callCount.Should().Be(1, "client function should be called once"); - updates.Should().Contain(u => u.Contents.Any(c => c is FunctionCallContent), "should contain function call"); - updates.Should().Contain(u => u.Contents.Any(c => c is FunctionResultContent), "should contain function result"); + Assert.Equal(1, callCount); + Assert.Contains(updates, u => u.Contents.Any(c => c is FunctionCallContent)); + Assert.Contains(updates, u => u.Contents.Any(c => c is FunctionResultContent)); var functionCallUpdates = updates.Where(u => u.Contents.Any(c => c is FunctionCallContent)).ToList(); - functionCallUpdates.Should().HaveCount(1); + Assert.Single(functionCallUpdates ?? []); var functionResultUpdates = updates.Where(u => u.Contents.Any(c => c is FunctionResultContent)).ToList(); - functionResultUpdates.Should().HaveCount(1); + Assert.Single(functionResultUpdates ?? []); - var resultContent = functionResultUpdates[0].Contents.OfType().First(); - resultContent.Result.Should().NotBeNull(); + FunctionResultContent resultContent = Assert.Single(updates.SelectMany(u => u.Contents.OfType())); + Assert.NotNull(resultContent.Result); } [Fact] @@ -193,19 +192,19 @@ public async Task ClientTriggersMultipleFunctionCallsAsync() } // Assert - calculateCallCount.Should().Be(1, "Calculate should be called once"); - formatCallCount.Should().Be(1, "FormatText should be called once"); + Assert.Equal(1, calculateCallCount); + Assert.Equal(1, formatCallCount); var functionCallUpdates = updates.Where(u => u.Contents.Any(c => c is FunctionCallContent)).ToList(); - functionCallUpdates.Should().NotBeEmpty("should contain function calls"); + Assert.NotEmpty(functionCallUpdates); var functionCalls = updates.SelectMany(u => u.Contents.OfType()).ToList(); - functionCalls.Should().HaveCount(2, "should have 2 function calls"); - functionCalls.Should().Contain(fc => fc.Name == "Calculate"); - functionCalls.Should().Contain(fc => fc.Name == "FormatText"); + Assert.Equal(2, functionCalls.Count); + Assert.Contains(functionCalls, fc => fc.Name == "Calculate"); + Assert.Contains(functionCalls, fc => fc.Name == "FormatText"); var functionResults = updates.SelectMany(u => u.Contents.OfType()).ToList(); - functionResults.Should().HaveCount(2, "should have 2 function results"); + Assert.Equal(2, functionResults.Count); } [Fact] @@ -263,29 +262,29 @@ public async Task ServerAndClientTriggerFunctionCallsSimultaneouslyAsync() // the streaming pipeline. This is now correct behavior thanks to // ConfigureForMixedInvocation in the AGUI.Hosting.AspNetCore package. - serverCallCount.Should().Be(1, "server function should execute on server"); - clientCallCount.Should().Be(1, "client function should execute on client"); + Assert.Equal(1, serverCallCount); + Assert.Equal(1, clientCallCount); var functionCallUpdates = updates.Where(u => u.Contents.Any(c => c is FunctionCallContent)).ToList(); - functionCallUpdates.Should().NotBeEmpty("should contain function calls"); + Assert.NotEmpty(functionCallUpdates); var functionCalls = updates.SelectMany(u => u.Contents.OfType()).ToList(); - functionCalls.Should().HaveCount(2, "should have 2 function calls"); - functionCalls.Should().Contain(fc => fc.Name == "GetServerData"); - functionCalls.Should().Contain(fc => fc.Name == "GetClientData"); + Assert.Equal(2, functionCalls.Count); + Assert.Contains(functionCalls, fc => fc.Name == "GetServerData"); + Assert.Contains(functionCalls, fc => fc.Name == "GetClientData"); var functionResults = updates.SelectMany(u => u.Contents.OfType()).ToList(); - functionResults.Should().HaveCount(2, "both server and client function results should be present"); + Assert.Equal(2, functionResults.Count); - var serverResult = functionResults.FirstOrDefault(fr => + FunctionResultContent? serverResult = functionResults.FirstOrDefault(fr => functionCalls.Any(fc => fc.Name == "GetServerData" && fc.CallId == fr.CallId)); - serverResult.Should().NotBeNull("server function call should have a result"); - serverResult!.Result?.ToString().Should().Contain("Server data"); + Assert.NotNull(serverResult); + Assert.Contains("Server data", serverResult!.Result?.ToString() ?? string.Empty); - var clientResult = functionResults.FirstOrDefault(fr => + FunctionResultContent? clientResult = functionResults.FirstOrDefault(fr => functionCalls.Any(fc => fc.Name == "GetClientData" && fc.CallId == fr.CallId)); - clientResult.Should().NotBeNull("client function call should have a result"); - clientResult!.Result?.ToString().Should().Contain("Client data"); + Assert.NotNull(clientResult); + Assert.Contains("Client data", clientResult!.Result?.ToString() ?? string.Empty); } [Fact] @@ -310,13 +309,13 @@ public async Task FunctionCallsPreserveCallIdAndNameAsync() // Assert var functionCallContent = updates.SelectMany(u => u.Contents.OfType()).FirstOrDefault(); - functionCallContent.Should().NotBeNull(); - functionCallContent!.CallId.Should().NotBeNullOrEmpty(); - functionCallContent.Name.Should().Be("TestFunction"); + Assert.NotNull(functionCallContent); + Assert.False(string.IsNullOrEmpty(functionCallContent!.CallId)); + Assert.Equal("TestFunction", functionCallContent.Name); var functionResultContent = updates.SelectMany(u => u.Contents.OfType()).FirstOrDefault(); - functionResultContent.Should().NotBeNull(); - functionResultContent!.CallId.Should().Be(functionCallContent.CallId, "result should have same call ID as the call"); + Assert.NotNull(functionResultContent); + Assert.Equal(functionCallContent.CallId, functionResultContent!.CallId); } [Fact] @@ -353,20 +352,21 @@ public async Task ParallelFunctionCallsFromServerAreHandledCorrectlyAsync() } // Assert - func1CallCount.Should().Be(1, "Function1 should be called once"); - func2CallCount.Should().Be(1, "Function2 should be called once"); + Assert.Equal(1, func1CallCount); + Assert.Equal(1, func2CallCount); var functionCalls = updates.SelectMany(u => u.Contents.OfType()).ToList(); - functionCalls.Should().HaveCount(2); - functionCalls.Select(fc => fc.Name).Should().Contain(s_expectedFunctionNames); + Assert.Equal(2, functionCalls.Count); + string[] functionNames = [.. functionCalls.Select(fc => fc.Name)]; + Assert.All(s_expectedFunctionNames, expectedName => Assert.Contains(expectedName, functionNames)); var functionResults = updates.SelectMany(u => u.Contents.OfType()).ToList(); - functionResults.Should().HaveCount(2); + Assert.Equal(2, functionResults.Count); // Each result should match its corresponding call ID foreach (var call in functionCalls) { - functionResults.Should().Contain(r => r.CallId == call.CallId); + Assert.Contains(functionResults, r => r.CallId == call.CallId); } } @@ -396,9 +396,9 @@ public async Task AGUIChatClientCombinesCustomJsonSerializerOptionsAsync() // Assert var jsonElement = JsonElement.Parse(json); - jsonElement.GetProperty("MaxTemp").GetInt32().Should().Be(75); - jsonElement.GetProperty("MinTemp").GetInt32().Should().Be(60); - jsonElement.GetProperty("Outlook").GetString().Should().Be("Rainy"); + Assert.Equal(75, jsonElement.GetProperty("MaxTemp").GetInt32()); + Assert.Equal(60, jsonElement.GetProperty("MinTemp").GetInt32()); + Assert.Equal("Rainy", jsonElement.GetProperty("Outlook").GetString()); this._output.WriteLine("Successfully serialized custom type: " + json); @@ -439,17 +439,17 @@ public async Task ServerToolCallWithCustomArgumentsAsync() } // Assert - callCount.Should().Be(1, "server function with custom arguments should be called once"); - updates.Should().Contain(u => u.Contents.Any(c => c is FunctionCallContent), "should contain function call"); - updates.Should().Contain(u => u.Contents.Any(c => c is FunctionResultContent), "should contain function result"); + Assert.Equal(1, callCount); + Assert.Contains(updates, u => u.Contents.Any(c => c is FunctionCallContent)); + Assert.Contains(updates, u => u.Contents.Any(c => c is FunctionResultContent)); var functionCallContent = updates.SelectMany(u => u.Contents.OfType()).FirstOrDefault(); - functionCallContent.Should().NotBeNull(); - functionCallContent!.Name.Should().Be("GetServerForecast"); + Assert.NotNull(functionCallContent); + Assert.Equal("GetServerForecast", functionCallContent!.Name); var functionResultContent = updates.SelectMany(u => u.Contents.OfType()).FirstOrDefault(); - functionResultContent.Should().NotBeNull(); - functionResultContent!.Result.Should().NotBeNull(); + Assert.NotNull(functionResultContent); + Assert.NotNull(functionResultContent!.Result); } [Fact] @@ -485,17 +485,17 @@ public async Task ClientToolCallWithCustomArgumentsAsync() } // Assert - callCount.Should().Be(1, "client function with custom arguments should be called once"); - updates.Should().Contain(u => u.Contents.Any(c => c is FunctionCallContent), "should contain function call"); - updates.Should().Contain(u => u.Contents.Any(c => c is FunctionResultContent), "should contain function result"); + Assert.Equal(1, callCount); + Assert.Contains(updates, u => u.Contents.Any(c => c is FunctionCallContent)); + Assert.Contains(updates, u => u.Contents.Any(c => c is FunctionResultContent)); var functionCallContent = updates.SelectMany(u => u.Contents.OfType()).FirstOrDefault(); - functionCallContent.Should().NotBeNull(); - functionCallContent!.Name.Should().Be("GetClientForecast"); + Assert.NotNull(functionCallContent); + Assert.Equal("GetClientForecast", functionCallContent!.Name); var functionResultContent = updates.SelectMany(u => u.Contents.OfType()).FirstOrDefault(); - functionResultContent.Should().NotBeNull(); - functionResultContent!.Result.Should().NotBeNull(); + Assert.NotNull(functionResultContent); + Assert.NotNull(functionResultContent!.Result); } private async Task SetupTestServerAsync( diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests/ConfigureAGUIJsonOptionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests/ConfigureAGUIJsonOptionsTests.cs index 9ffe491bf63..4adbf485a3e 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests/ConfigureAGUIJsonOptionsTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests/ConfigureAGUIJsonOptionsTests.cs @@ -2,7 +2,6 @@ using System.Text.Json; using AGUI.Abstractions; -using FluentAssertions; using Microsoft.Extensions.AI; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -21,7 +20,7 @@ public void AddAGUIServer_ConfiguresJsonOptions_ResolvesAGUIWireTypes() // 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(); + Assert.Null(Record.Exception(()=>options.GetTypeInfo(typeof(RunStartedEvent)))); } [Fact] @@ -30,7 +29,7 @@ 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(); + Assert.Null(Record.Exception(()=>options.GetTypeInfo(typeof(ChatMessage)))); } private static JsonSerializerOptions BuildConfiguredSerializerOptions() diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests.csproj b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests.csproj index ed65db63289..8497c2d761d 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests.csproj +++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests.csproj @@ -5,7 +5,6 @@ - diff --git a/dotnet/tests/Microsoft.Agents.AI.Mcp.UnitTests/ListAgentToolsWithTasksTests.cs b/dotnet/tests/Microsoft.Agents.AI.Mcp.UnitTests/ListAgentToolsWithTasksTests.cs index 1c3c9695047..5a407460a67 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Mcp.UnitTests/ListAgentToolsWithTasksTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Mcp.UnitTests/ListAgentToolsWithTasksTests.cs @@ -3,7 +3,6 @@ using System; using System.Linq; using System.Threading.Tasks; -using FluentAssertions; using ModelContextProtocol.Server; namespace Microsoft.Agents.AI.Mcp.UnitTests; @@ -24,9 +23,9 @@ public async Task ListAgentToolsWithTasks_WrapsAllToolsAsync() var result = await fixture.Client.ListAgentToolsWithTasksAsync(); // Assert - result.Should().HaveCount(2); - result.Should().AllBeOfType(); - result.Select(tool => tool.Name).Should().Equal("first", "second"); + Assert.Equal(2, result.Count); + Assert.All(result, tool => Assert.IsType(tool)); + Assert.Equal(["first", "second"], result.Select(tool => tool.Name)); } [Fact] @@ -39,7 +38,7 @@ public async Task ListAgentToolsWithTasks_ThrowsOnNullClientAsync() Func act = async () => await client.ListAgentToolsWithTasksAsync(); // Assert - await act.Should().ThrowAsync(); + await Assert.ThrowsAsync(act); } [Fact] @@ -56,7 +55,7 @@ public async Task ListAgentToolsWithTasks_NonPositiveStuckPollLimit_ThrowsAsync( Func act = async () => await fixture.Client.ListAgentToolsWithTasksAsync(options); // Assert - await act.Should().ThrowAsync(); + await Assert.ThrowsAsync(act); } [Fact] @@ -73,7 +72,7 @@ public async Task ListAgentToolsWithTasks_NonPositiveInputRequestLimit_ThrowsAsy Func act = async () => await fixture.Client.ListAgentToolsWithTasksAsync(options); // Assert - await act.Should().ThrowAsync(); + await Assert.ThrowsAsync(act); } [Fact] @@ -90,7 +89,7 @@ public async Task ListAgentToolsWithTasks_NonPositiveCancellationTimeout_ThrowsA Func act = async () => await fixture.Client.ListAgentToolsWithTasksAsync(options); // Assert - await act.Should().ThrowAsync(); + await Assert.ThrowsAsync(act); } [Fact] @@ -107,7 +106,7 @@ public async Task ListAgentToolsWithTasks_SubMillisecondCancellationTimeout_Thro Func act = async () => await fixture.Client.ListAgentToolsWithTasksAsync(options); // Assert - await act.Should().ThrowAsync(); + await Assert.ThrowsAsync(act); } [Fact] @@ -128,7 +127,7 @@ public async Task ListAgentToolsWithTasks_InvalidPollingIntervalRange_ThrowsAsyn Func act = async () => await fixture.Client.ListAgentToolsWithTasksAsync(options); // Assert - await act.Should().ThrowAsync(); + await Assert.ThrowsAsync(act); } [Fact] @@ -149,7 +148,7 @@ public async Task ListAgentToolsWithTasks_PollingRangeWithoutWholeMillisecond_Th Func act = async () => await fixture.Client.ListAgentToolsWithTasksAsync(options); // Assert - await act.Should().ThrowAsync(); + await Assert.ThrowsAsync(act); } [Fact] @@ -169,6 +168,6 @@ public async Task ListAgentToolsWithTasks_PollingMaximumAboveRuntimeLimit_Throws Func act = async () => await fixture.Client.ListAgentToolsWithTasksAsync(options); // Assert - await act.Should().ThrowAsync(); + await Assert.ThrowsAsync(act); } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Mcp.UnitTests/McpTaskOptionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Mcp.UnitTests/McpTaskOptionsTests.cs index 19b3eee9bef..30c85e750ca 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Mcp.UnitTests/McpTaskOptionsTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Mcp.UnitTests/McpTaskOptionsTests.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. using System; -using FluentAssertions; namespace Microsoft.Agents.AI.Mcp.UnitTests; @@ -14,11 +13,11 @@ public void Defaults_AreSane() McpTaskOptions options = new(); // Assert - options.CancelRemoteTaskOnLocalCancellation.Should().BeTrue(); - options.MaxConsecutiveStuckPolls.Should().Be(60); - options.MaxTotalInputRequests.Should().Be(100); - options.RemoteCancellationTimeout.Should().Be(TimeSpan.FromSeconds(5)); - options.MinimumPollingInterval.Should().Be(TimeSpan.FromMilliseconds(10)); - options.MaximumPollingInterval.Should().Be(TimeSpan.FromMilliseconds(uint.MaxValue - 1L)); + Assert.True(options.CancelRemoteTaskOnLocalCancellation); + Assert.Equal(60, options.MaxConsecutiveStuckPolls); + Assert.Equal(100, options.MaxTotalInputRequests); + Assert.Equal(TimeSpan.FromSeconds(5), options.RemoteCancellationTimeout); + Assert.Equal(TimeSpan.FromMilliseconds(10), options.MinimumPollingInterval); + Assert.Equal(TimeSpan.FromMilliseconds(uint.MaxValue - 1L), options.MaximumPollingInterval); } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Mcp.UnitTests/Microsoft.Agents.AI.Mcp.UnitTests.csproj b/dotnet/tests/Microsoft.Agents.AI.Mcp.UnitTests/Microsoft.Agents.AI.Mcp.UnitTests.csproj index d5763b5e379..843b21f28d6 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Mcp.UnitTests/Microsoft.Agents.AI.Mcp.UnitTests.csproj +++ b/dotnet/tests/Microsoft.Agents.AI.Mcp.UnitTests/Microsoft.Agents.AI.Mcp.UnitTests.csproj @@ -6,7 +6,6 @@ - diff --git a/dotnet/tests/Microsoft.Agents.AI.Mcp.UnitTests/TaskAwareMcpClientAIFunctionTests.cs b/dotnet/tests/Microsoft.Agents.AI.Mcp.UnitTests/TaskAwareMcpClientAIFunctionTests.cs index 354e66390a4..c7f867fa82a 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Mcp.UnitTests/TaskAwareMcpClientAIFunctionTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Mcp.UnitTests/TaskAwareMcpClientAIFunctionTests.cs @@ -6,7 +6,6 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.Extensions.AI; using ModelContextProtocol.Client; using ModelContextProtocol.Protocol; @@ -34,10 +33,9 @@ public async Task InvokeAsync_TaskBackedTool_ReturnsResultAsync() object? result = await wrapped.InvokeAsync(arguments: null, CancellationToken.None); // Assert - result.Should().BeOfType() - .Which.Text.Should().Be("task-result"); - fixture.CreatedTaskCount.Should().Be(1); - fixture.PollCount.Should().BeGreaterThan(0); + Assert.Equal("task-result", Assert.IsType(result).Text); + Assert.Equal(1, fixture.CreatedTaskCount); + Assert.True((fixture.PollCount) > (0)); } [Theory] @@ -65,11 +63,11 @@ public async Task InvokeAsync_InvalidInitialPollInterval_CancelsRemoteTaskAsync( Func act = async () => await wrapped.InvokeAsync(arguments: null, CancellationToken.None); // Assert - await act.Should().ThrowAsync() - .WithMessage($"*pollIntervalMs of {pollIntervalMs}*"); + ModelContextProtocol.McpException exception = await Assert.ThrowsAsync(act); + Assert.Contains($"pollIntervalMs of {pollIntervalMs}", exception.Message); await fixture.RemoteCancellationObserved.WaitAsync(TimeSpan.FromSeconds(5)); - fixture.SuccessfulCancellationTransitionCount.Should().Be(1); - fixture.CancellationRequestCount.Should().Be(1); + Assert.Equal(1, fixture.SuccessfulCancellationTransitionCount); + Assert.Equal(1, fixture.CancellationRequestCount); } [Fact] @@ -94,11 +92,11 @@ public async Task InvokeAsync_InvalidUpdatedPollInterval_CancelsRemoteTaskAsync( Func act = async () => await wrapped.InvokeAsync(arguments: null, CancellationToken.None); // Assert - await act.Should().ThrowAsync() - .WithMessage("*pollIntervalMs of 0*"); + ModelContextProtocol.McpException exception = await Assert.ThrowsAsync(act); + Assert.Contains("pollIntervalMs of 0", exception.Message); await fixture.RemoteCancellationObserved.WaitAsync(TimeSpan.FromSeconds(5)); - fixture.SuccessfulCancellationTransitionCount.Should().Be(1); - fixture.CancellationRequestCount.Should().Be(1); + Assert.Equal(1, fixture.SuccessfulCancellationTransitionCount); + Assert.Equal(1, fixture.CancellationRequestCount); } [Fact] @@ -135,7 +133,7 @@ public async Task InvokeAsync_ConfiguredPollingRange_AcceptsShortServerIntervalA object? result = await invocation; // Assert - result.Should().BeOfType().Which.Text.Should().Be("completed"); + Assert.Equal("completed", Assert.IsType(result).Text); } finally { @@ -176,7 +174,7 @@ public async Task InvokeAsync_MissingPollInterval_ConstrainsFallbackToConfigured object? result = await invocation; // Assert - result.Should().BeOfType().Which.Text.Should().Be("completed"); + Assert.Equal("completed", Assert.IsType(result).Text); } finally { @@ -198,9 +196,8 @@ public async Task InvokeAsync_ServerWithoutTasks_ReturnsInlineResultAsync() object? result = await wrapped.InvokeAsync(arguments: null, CancellationToken.None); // Assert - result.Should().BeOfType() - .Which.Text.Should().Be("inline-result"); - fixture.CreatedTaskCount.Should().Be(0); + Assert.Equal("inline-result", Assert.IsType(result).Text); + Assert.Equal(0, fixture.CreatedTaskCount); } [Fact] @@ -247,10 +244,9 @@ public async Task InvokeAsync_InputRequired_DispatchesClientHandlerAsync() object? result = await wrapped.InvokeAsync(arguments: null, CancellationToken.None); // Assert - result.Should().BeOfType() - .Which.Text.Should().Be("accept:yes"); - fixture.CreatedTaskCount.Should().Be(1); - fixture.InputRequestCount.Should().Be(1); + Assert.Equal("accept:yes", Assert.IsType(result).Text); + Assert.Equal(1, fixture.CreatedTaskCount); + Assert.Equal(1, fixture.InputRequestCount); } [Fact] @@ -284,10 +280,10 @@ public async Task InvokeAsync_ForwardsNullPrimitiveAndComplexArgumentsAsync() _ = await wrapped.InvokeAsync(arguments, CancellationToken.None); // Assert - observedArguments.Should().NotBeNull(); - observedArguments!["optional"].ValueKind.Should().Be(JsonValueKind.Null); - observedArguments["count"].GetInt32().Should().Be(3); - observedArguments["payload"].GetProperty("label").GetString().Should().Be("nested"); + Assert.NotNull(observedArguments); + Assert.Equal(JsonValueKind.Null, observedArguments!["optional"].ValueKind); + Assert.Equal(3, observedArguments["count"].GetInt32()); + Assert.Equal("nested", observedArguments["payload"].GetProperty("label").GetString()); } [Fact] @@ -306,7 +302,7 @@ public async Task InvokeAsync_SimpleResult_MatchesMcpClientToolProjectionAsync() object? wrappedResult = await wrapped.InvokeAsync(arguments: null, CancellationToken.None); // Assert - wrappedResult.Should().BeEquivalentTo(innerResult); + Assert.Equivalent(innerResult, wrappedResult); } [Fact] @@ -329,9 +325,9 @@ public async Task InvokeAsync_ToolError_PreservesCallToolResultEnvelopeAsync() object? result = await wrapped.InvokeAsync(arguments: null, CancellationToken.None); // Assert - JsonElement payload = result.Should().BeOfType().Subject; - payload.GetProperty("isError").GetBoolean().Should().BeTrue(); - payload.GetProperty("content")[0].GetProperty("text").GetString().Should().Be("tool failed"); + JsonElement payload = Assert.IsType(result); + Assert.True(payload.GetProperty("isError").GetBoolean()); + Assert.Equal("tool failed", payload.GetProperty("content")[0].GetProperty("text").GetString()); } [Fact] @@ -362,10 +358,10 @@ await fixture.FailLatestTaskAsync( Func act = async () => await invocation; // Assert - await act.Should().ThrowAsync() - .WithMessage("*simulated failure*"); - fixture.SuccessfulCancellationTransitionCount.Should().Be(0); - fixture.CancellationRequestCount.Should().Be(0); + ModelContextProtocol.McpException exception = await Assert.ThrowsAsync(act); + Assert.Contains("simulated failure", exception.Message); + Assert.Equal(0, fixture.SuccessfulCancellationTransitionCount); + Assert.Equal(0, fixture.CancellationRequestCount); } finally { @@ -400,10 +396,10 @@ public async Task InvokeAsync_ServerCancelledTask_ThrowsOperationCanceledAsync() Func act = async () => await invocation; // Assert - await act.Should().ThrowAsync() - .WithMessage("*cancelled by the server*"); - fixture.SuccessfulCancellationTransitionCount.Should().Be(1); - fixture.CancellationRequestCount.Should().Be(0); + OperationCanceledException exception = await Assert.ThrowsAsync(act); + Assert.Contains("cancelled by the server", exception.Message); + Assert.Equal(1, fixture.SuccessfulCancellationTransitionCount); + Assert.Equal(0, fixture.CancellationRequestCount); } finally { @@ -446,11 +442,11 @@ public async Task InvokeAsync_InputHandlerFailure_CancelsRemoteTaskAsync() Func act = async () => await wrapped.InvokeAsync(arguments: null, CancellationToken.None); // Assert - await act.Should().ThrowAsync() - .WithMessage("input handler failed"); + InvalidOperationException exception = await Assert.ThrowsAsync(act); + Assert.Equal("input handler failed", exception.Message); await fixture.RemoteCancellationObserved.WaitAsync(TimeSpan.FromSeconds(5)); - fixture.SuccessfulCancellationTransitionCount.Should().Be(1); - fixture.CancellationRequestCount.Should().Be(1); + Assert.Equal(1, fixture.SuccessfulCancellationTransitionCount); + Assert.Equal(1, fixture.CancellationRequestCount); } [Fact] @@ -475,11 +471,11 @@ public async Task InvokeAsync_GetTaskFailure_CancelsRemoteTaskAndPreservesProtoc Func act = async () => await wrapped.InvokeAsync(arguments: null, CancellationToken.None); // Assert - await act.Should().ThrowAsync() - .WithMessage("Request failed (remote): An error occurred."); + ModelContextProtocol.McpProtocolException exception = await Assert.ThrowsAsync(act); + Assert.Equal("Request failed (remote): An error occurred.", exception.Message); await fixture.RemoteCancellationObserved.WaitAsync(TimeSpan.FromSeconds(5)); - fixture.SuccessfulCancellationTransitionCount.Should().Be(1); - fixture.CancellationRequestCount.Should().Be(1); + Assert.Equal(1, fixture.SuccessfulCancellationTransitionCount); + Assert.Equal(1, fixture.CancellationRequestCount); } [Fact] @@ -518,11 +514,11 @@ public async Task InvokeAsync_UpdateTaskFailure_CancelsRemoteTaskAndPreservesPro Func act = async () => await wrapped.InvokeAsync(arguments: null, CancellationToken.None); // Assert - await act.Should().ThrowAsync() - .WithMessage("Request failed (remote): An error occurred."); + ModelContextProtocol.McpProtocolException exception = await Assert.ThrowsAsync(act); + Assert.Equal("Request failed (remote): An error occurred.", exception.Message); await fixture.RemoteCancellationObserved.WaitAsync(TimeSpan.FromSeconds(5)); - fixture.SuccessfulCancellationTransitionCount.Should().Be(1); - fixture.CancellationRequestCount.Should().Be(1); + Assert.Equal(1, fixture.SuccessfulCancellationTransitionCount); + Assert.Equal(1, fixture.CancellationRequestCount); } [Fact] @@ -552,9 +548,9 @@ public async Task InvokeAsync_MalformedCompletedResult_DoesNotCancelTerminalTask Func act = async () => await invocation; // Assert - await act.Should().ThrowAsync(); - fixture.SuccessfulCancellationTransitionCount.Should().Be(0); - fixture.CancellationRequestCount.Should().Be(0); + await Assert.ThrowsAsync(act); + Assert.Equal(0, fixture.SuccessfulCancellationTransitionCount); + Assert.Equal(0, fixture.CancellationRequestCount); } finally { @@ -599,11 +595,11 @@ public async Task InvokeAsync_StuckInputRequired_CancelsRemoteTaskAsync() Func act = async () => await wrapped.InvokeAsync(arguments: null, CancellationToken.None); // Assert - await act.Should().ThrowAsync() - .WithMessage("*2 consecutive polls*"); + ModelContextProtocol.McpException exception = await Assert.ThrowsAsync(act); + Assert.Contains("2 consecutive polls", exception.Message); await fixture.RemoteCancellationObserved.WaitAsync(TimeSpan.FromSeconds(5)); - fixture.SuccessfulCancellationTransitionCount.Should().Be(1); - fixture.CancellationRequestCount.Should().Be(1); + Assert.Equal(1, fixture.SuccessfulCancellationTransitionCount); + Assert.Equal(1, fixture.CancellationRequestCount); } [Fact] @@ -650,10 +646,10 @@ public async Task InvokeAsync_InputRequestsAtLimit_CompletesAsync() object? result = await wrapped.InvokeAsync(arguments: null, CancellationToken.None); // Assert - result.Should().BeOfType().Which.Text.Should().Be("completed"); - handledInputRequests.Should().Be(2); - fixture.SuccessfulCancellationTransitionCount.Should().Be(0); - fixture.CancellationRequestCount.Should().Be(0); + Assert.Equal("completed", Assert.IsType(result).Text); + Assert.Equal(2, handledInputRequests); + Assert.Equal(0, fixture.SuccessfulCancellationTransitionCount); + Assert.Equal(0, fixture.CancellationRequestCount); } [Fact] @@ -700,12 +696,12 @@ public async Task InvokeAsync_InputRequestLimitExceeded_CancelsBeforeDispatchAsy Func act = async () => await wrapped.InvokeAsync(arguments: null, CancellationToken.None); // Assert - await act.Should().ThrowAsync() - .WithMessage("*limit of 2 unique input requests*"); - handledInputRequests.Should().Be(2); + ModelContextProtocol.McpException exception = await Assert.ThrowsAsync(act); + Assert.Contains("limit of 2 unique input requests", exception.Message); + Assert.Equal(2, handledInputRequests); await fixture.RemoteCancellationObserved.WaitAsync(TimeSpan.FromSeconds(5)); - fixture.SuccessfulCancellationTransitionCount.Should().Be(1); - fixture.CancellationRequestCount.Should().Be(1); + Assert.Equal(1, fixture.SuccessfulCancellationTransitionCount); + Assert.Equal(1, fixture.CancellationRequestCount); } [Fact] @@ -742,13 +738,13 @@ public async Task InvokeAsync_LocalCancellation_CancelsRemoteTaskAsync() Func act = async () => await invocation; // Assert - await act.Should().ThrowAsync(); + await Assert.ThrowsAnyAsync(act); await fixture.RemoteCancellationObserved.WaitAsync(TimeSpan.FromSeconds(5)); await serverCancelled.Task.WaitAsync(TimeSpan.FromSeconds(5)); - fixture.CreatedTaskCount.Should().Be(1); - fixture.PollCount.Should().BeGreaterThan(0); - fixture.SuccessfulCancellationTransitionCount.Should().Be(1); - fixture.CancellationRequestCount.Should().Be(1); + Assert.Equal(1, fixture.CreatedTaskCount); + Assert.True((fixture.PollCount) > (0)); + Assert.Equal(1, fixture.SuccessfulCancellationTransitionCount); + Assert.Equal(1, fixture.CancellationRequestCount); } [Fact] @@ -780,9 +776,9 @@ public async Task InvokeAsync_LocalCancellation_DoesNotCancelRemoteTaskWhenDisab Func act = async () => await invocation; // Assert - await act.Should().ThrowAsync(); - fixture.SuccessfulCancellationTransitionCount.Should().Be(0); - fixture.CancellationRequestCount.Should().Be(0); + await Assert.ThrowsAnyAsync(act); + Assert.Equal(0, fixture.SuccessfulCancellationTransitionCount); + Assert.Equal(0, fixture.CancellationRequestCount); } finally { diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.Mcp.UnitTests/DefaultMcpToolHandlerTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.Mcp.UnitTests/DefaultMcpToolHandlerTests.cs index d6471e5d5a3..e99416c5378 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.Mcp.UnitTests/DefaultMcpToolHandlerTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.Mcp.UnitTests/DefaultMcpToolHandlerTests.cs @@ -7,7 +7,6 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.Extensions.AI; using ModelContextProtocol.Protocol; @@ -27,7 +26,7 @@ public async Task Constructor_WithNoParameters_ShouldCreateInstanceAsync() DefaultMcpToolHandler handler = new(); // Assert - handler.Should().NotBeNull(); + Assert.NotNull(handler); await handler.DisposeAsync(); } @@ -38,7 +37,7 @@ public async Task Constructor_WithNullHttpClientProvider_ShouldCreateInstanceAsy DefaultMcpToolHandler handler = new(httpClientProvider: null); // Assert - handler.Should().NotBeNull(); + Assert.NotNull(handler); await handler.DisposeAsync(); } @@ -52,7 +51,7 @@ public async Task Constructor_WithHttpClientProvider_ShouldCreateInstanceAsync() DefaultMcpToolHandler handler = new(httpClientProvider: ProviderAsync); // Assert - handler.Should().NotBeNull(); + Assert.NotNull(handler); await handler.DisposeAsync(); } @@ -70,7 +69,7 @@ public async Task DisposeAsync_WhenCalled_ShouldCompleteWithoutErrorAsync() Func act = async () => await handler.DisposeAsync(); // Assert - await act.Should().NotThrowAsync(); + Assert.Null(await Record.ExceptionAsync(act)); } [Fact] @@ -84,7 +83,7 @@ public async Task DisposeAsync_WhenCalledMultipleTimes_ShouldHandleGracefullyAsy Func act = async () => await handler.DisposeAsync(); // Assert - Second dispose should throw ObjectDisposedException from the semaphore - await act.Should().ThrowAsync(); + await Assert.ThrowsAsync(act); } #endregion @@ -128,8 +127,8 @@ await handler.InvokeToolAsync( } // Assert - providerCalled.Should().BeTrue(); - capturedServerUrl.Should().Be("http://localhost:12345/mcp"); + Assert.True(providerCalled); + Assert.Equal("http://localhost:12345/mcp", capturedServerUrl); } [Fact] @@ -170,7 +169,7 @@ await handler.InvokeToolAsync( } // Assert - providerCalled.Should().BeTrue(); + Assert.True(providerCalled); } #endregion @@ -216,7 +215,7 @@ await handler.InvokeToolAsync( } // Assert - Provider is called each time because McpClient creation fails before caching - providerCallCount.Should().Be(2); + Assert.Equal(2, providerCallCount); } finally { @@ -260,7 +259,7 @@ await handler.InvokeToolAsync( } // Assert - Provider should be called once per unique server URL - providerCallCount.Should().Be(2); + Assert.Equal(2, providerCallCount); } finally { @@ -311,7 +310,7 @@ await handler.InvokeToolAsync( } // Assert - Different headers should create different cache keys - providerCallCount.Should().Be(2); + Assert.Equal(2, providerCallCount); } finally { @@ -330,7 +329,7 @@ public void ComputeHeadersHash_WithNullHeaders_ReturnsEmptyString() string result = DefaultMcpToolHandler.ComputeHeadersHash(null); // Assert - result.Should().BeEmpty(); + Assert.Equal(string.Empty, result); } [Fact] @@ -340,7 +339,7 @@ public void ComputeHeadersHash_WithEmptyHeaders_ReturnsEmptyString() string result = DefaultMcpToolHandler.ComputeHeadersHash(new Dictionary()); // Assert - result.Should().BeEmpty(); + Assert.Equal(string.Empty, result); } [Fact] @@ -363,7 +362,7 @@ public void ComputeHeadersHash_SameHeadersDifferentOrder_ReturnsSameHash() string hash2 = DefaultMcpToolHandler.ComputeHeadersHash(headers2); // Assert - hash1.Should().Be(hash2); + Assert.Equal(hash2, hash1); } [Fact] @@ -378,7 +377,7 @@ public void ComputeHeadersHash_SameKeysDifferentCaseKeys_ReturnsSameHash() string hash2 = DefaultMcpToolHandler.ComputeHeadersHash(headers2); // Assert - hash1.Should().Be(hash2); + Assert.Equal(hash2, hash1); } [Fact] @@ -393,7 +392,7 @@ public void ComputeHeadersHash_SameKeysDifferentCaseValues_ReturnsDifferentHash( string hash2 = DefaultMcpToolHandler.ComputeHeadersHash(headers2); // Assert - hash1.Should().NotBe(hash2); + Assert.NotEqual(hash2, hash1); } [Fact] @@ -408,7 +407,7 @@ public void ComputeHeadersHash_DifferentHeaders_ReturnsDifferentHash() string hash2 = DefaultMcpToolHandler.ComputeHeadersHash(headers2); // Assert - hash1.Should().NotBe(hash2); + Assert.NotEqual(hash2, hash1); } #endregion @@ -433,7 +432,7 @@ public void BuildCacheKey_SameInputs_ReturnsEqualKeys() var key2 = DefaultMcpToolHandler.BuildCacheKey("http://localhost/mcp", "label", "conn", headers); // Assert - key1.Should().Be(key2); + Assert.Equal(key2, key1); } [Fact] @@ -444,9 +443,9 @@ public void BuildCacheKey_DifferentConnectionName_ReturnsDifferentKeys() var key2 = DefaultMcpToolHandler.BuildCacheKey("http://localhost/mcp", "label", "connection-b", null); // Assert - key1.Should().NotBe(key2); - key1.Connection.Should().Be("connection-a"); - key2.Connection.Should().Be("connection-b"); + Assert.NotEqual(key2, key1); + Assert.Equal("connection-a", key1.Connection); + Assert.Equal("connection-b", key2.Connection); } [Fact] @@ -457,9 +456,9 @@ public void BuildCacheKey_DifferentServerLabel_ReturnsDifferentKeys() var key2 = DefaultMcpToolHandler.BuildCacheKey("http://localhost/mcp", "label-b", null, null); // Assert - key1.Should().NotBe(key2); - key1.Label.Should().Be("label-a"); - key2.Label.Should().Be("label-b"); + Assert.NotEqual(key2, key1); + Assert.Equal("label-a", key1.Label); + Assert.Equal("label-b", key2.Label); } [Fact] @@ -471,7 +470,7 @@ public void BuildCacheKey_CaseSensitiveUrlPath_ReturnsDifferentKeys() var key2 = DefaultMcpToolHandler.BuildCacheKey("http://localhost/tools", null, null, null); // Assert - key1.Should().NotBe(key2); + Assert.NotEqual(key2, key1); } [Fact] @@ -486,8 +485,8 @@ public void BuildCacheKey_HeaderValuesCaseSensitive_ReturnsDifferentKeys() var key2 = DefaultMcpToolHandler.BuildCacheKey("http://localhost/mcp", null, null, headers2); // Assert — header value case must propagate into the cache key - key1.Should().NotBe(key2); - key1.HeadersHash.Should().NotBe(key2.HeadersHash); + Assert.NotEqual(key2, key1); + Assert.NotEqual(key2.HeadersHash, key1.HeadersHash); } [Fact] @@ -497,9 +496,9 @@ public void BuildCacheKey_NullLabelAndConnection_NormalizesToEmptyString() var key = DefaultMcpToolHandler.BuildCacheKey("http://localhost/mcp", null, null, null); // Assert — verifies null-safety contract callers rely on - key.Label.Should().BeEmpty(); - key.Connection.Should().BeEmpty(); - key.HeadersHash.Should().BeEmpty(); + Assert.Equal(string.Empty, key.Label); + Assert.Equal(string.Empty, key.Connection); + Assert.Equal(string.Empty, key.HeadersHash); } #endregion @@ -513,7 +512,7 @@ public void IsListToolsToolName_WithReservedName_ShouldReturnTrue() bool result = DefaultMcpToolHandler.IsListToolsToolName(DefaultMcpToolHandler.ListToolsToolName); // Assert - result.Should().BeTrue(); + Assert.True(result); } [Fact] @@ -523,7 +522,7 @@ public void IsListToolsToolName_WithRegularToolName_ShouldReturnFalse() bool result = DefaultMcpToolHandler.IsListToolsToolName("search"); // Assert - result.Should().BeFalse(); + Assert.False(result); } [Fact] @@ -544,8 +543,8 @@ public async Task InvokeToolAsync_WithListToolsArguments_ShouldThrowArgumentExce connectionName: null); // Assert - await act.Should().ThrowAsync() - .WithMessage("*does not accept tool arguments*"); + ArgumentException exception = await Assert.ThrowsAsync(act); + Assert.Contains("does not accept tool arguments", exception.Message); } finally { @@ -580,12 +579,12 @@ public async Task CreateListToolsResultContent_WithTools_ShouldSerializeToolMeta McpServerToolResultContent result = DefaultMcpToolHandler.CreateListToolsResultContent([tool]); // Assert - TextContent text = result.Outputs.Should().ContainSingle().Subject.Should().BeOfType().Subject; + TextContent text = Assert.IsType(Assert.Single(result.Outputs ?? [])); using JsonDocument document = JsonDocument.Parse(text.Text); JsonElement listedTool = document.RootElement.GetProperty("tools")[0]; - listedTool.GetProperty("name").GetString().Should().Be("search"); - listedTool.GetProperty("description").GetString().Should().Be("Searches documentation."); - listedTool.GetProperty("inputSchema").GetProperty("properties").GetProperty("query").GetProperty("type").GetString().Should().Be("string"); + Assert.Equal("search", listedTool.GetProperty("name").GetString()); + Assert.Equal("Searches documentation.", listedTool.GetProperty("description").GetString()); + Assert.Equal("string", listedTool.GetProperty("inputSchema").GetProperty("properties").GetProperty("query").GetProperty("type").GetString()); } #endregion @@ -599,7 +598,7 @@ public async Task DefaultMcpToolHandler_ShouldImplementIMcpToolHandlerAsync() DefaultMcpToolHandler handler = new(); // Assert - handler.Should().BeAssignableTo(); + Assert.IsAssignableFrom(handler); await handler.DisposeAsync(); } @@ -610,7 +609,7 @@ public async Task DefaultMcpToolHandler_ShouldImplementIAsyncDisposableAsync() DefaultMcpToolHandler handler = new(); // Assert - handler.Should().BeAssignableTo(); + Assert.IsAssignableFrom(handler); await handler.DisposeAsync(); } @@ -628,9 +627,9 @@ public void ConvertContentBlock_TextContentBlock_ShouldReturnTextContent() AIContent result = DefaultMcpToolHandler.ConvertContentBlock(block); // Assert - TextContent textContent = result.Should().BeOfType().Subject; - textContent.Text.Should().Be("hello world"); - textContent.RawRepresentation.Should().BeSameAs(block); + TextContent textContent = Assert.IsType(result); + Assert.Equal("hello world", textContent.Text); + Assert.Same(block, textContent.RawRepresentation); } [Fact] @@ -643,11 +642,11 @@ public void ConvertContentBlock_ImageContentBlock_WithEmptyData_ShouldReturnData AIContent result = DefaultMcpToolHandler.ConvertContentBlock(block); // Assert - DataContent dataContent = result.Should().BeOfType().Subject; - dataContent.MediaType.Should().Be("image/png"); - dataContent.Uri.Should().Be("data:image/png;base64,"); - dataContent.Data.IsEmpty.Should().BeTrue(); - dataContent.RawRepresentation.Should().BeSameAs(block); + DataContent dataContent = Assert.IsType(result); + Assert.Equal("image/png", dataContent.MediaType); + Assert.Equal("data:image/png;base64,", dataContent.Uri); + Assert.True(dataContent.Data.IsEmpty); + Assert.Same(block, dataContent.RawRepresentation); } [Fact] @@ -663,11 +662,11 @@ public void ConvertContentBlock_ImageContentBlock_WithBase64Payload_ShouldReturn AIContent result = DefaultMcpToolHandler.ConvertContentBlock(block); // Assert - DataContent dataContent = result.Should().BeOfType().Subject; - dataContent.MediaType.Should().Be("image/png"); - dataContent.Data.ToArray().Should().BeEquivalentTo(expectedDecoded); - dataContent.Uri.Should().Be($"data:image/png;base64,{Base64Payload}"); - dataContent.RawRepresentation.Should().BeSameAs(block); + DataContent dataContent = Assert.IsType(result); + Assert.Equal("image/png", dataContent.MediaType); + Assert.Equivalent(expectedDecoded, dataContent.Data.ToArray()); + Assert.Equal($"data:image/png;base64,{Base64Payload}", dataContent.Uri); + Assert.Same(block, dataContent.RawRepresentation); } [Fact] @@ -680,11 +679,11 @@ public void ConvertContentBlock_AudioContentBlock_WithEmptyData_ShouldReturnData AIContent result = DefaultMcpToolHandler.ConvertContentBlock(block); // Assert - DataContent dataContent = result.Should().BeOfType().Subject; - dataContent.MediaType.Should().Be("audio/wav"); - dataContent.Uri.Should().Be("data:audio/wav;base64,"); - dataContent.Data.IsEmpty.Should().BeTrue(); - dataContent.RawRepresentation.Should().BeSameAs(block); + DataContent dataContent = Assert.IsType(result); + Assert.Equal("audio/wav", dataContent.MediaType); + Assert.Equal("data:audio/wav;base64,", dataContent.Uri); + Assert.True(dataContent.Data.IsEmpty); + Assert.Same(block, dataContent.RawRepresentation); } [Fact] @@ -700,11 +699,11 @@ public void ConvertContentBlock_AudioContentBlock_WithBase64Payload_ShouldReturn AIContent result = DefaultMcpToolHandler.ConvertContentBlock(block); // Assert - DataContent dataContent = result.Should().BeOfType().Subject; - dataContent.MediaType.Should().Be("audio/wav"); - dataContent.Data.ToArray().Should().BeEquivalentTo(expectedDecoded); - dataContent.Uri.Should().Be($"data:audio/wav;base64,{Base64Payload}"); - dataContent.RawRepresentation.Should().BeSameAs(block); + DataContent dataContent = Assert.IsType(result); + Assert.Equal("audio/wav", dataContent.MediaType); + Assert.Equivalent(expectedDecoded, dataContent.Data.ToArray()); + Assert.Equal($"data:audio/wav;base64,{Base64Payload}", dataContent.Uri); + Assert.Same(block, dataContent.RawRepresentation); } [Fact] @@ -725,9 +724,9 @@ public void ConvertContentBlock_EmbeddedResourceBlock_WithTextResource_ShouldRet AIContent result = DefaultMcpToolHandler.ConvertContentBlock(block); // Assert - TextContent textContent = result.Should().BeOfType().Subject; - textContent.Text.Should().Be("embedded text payload"); - textContent.RawRepresentation.Should().BeSameAs(block); + TextContent textContent = Assert.IsType(result); + Assert.Equal("embedded text payload", textContent.Text); + Assert.Same(block, textContent.RawRepresentation); } [Fact] @@ -751,11 +750,11 @@ public void ConvertContentBlock_EmbeddedResourceBlock_WithBlobResource_ShouldRet AIContent result = DefaultMcpToolHandler.ConvertContentBlock(block); // Assert - DataContent dataContent = result.Should().BeOfType().Subject; - dataContent.MediaType.Should().Be("application/zip"); - dataContent.Data.ToArray().Should().BeEquivalentTo(expectedDecoded); - dataContent.Uri.Should().Be($"data:application/zip;base64,{Base64Payload}"); - dataContent.RawRepresentation.Should().BeSameAs(block); + DataContent dataContent = Assert.IsType(result); + Assert.Equal("application/zip", dataContent.MediaType); + Assert.Equivalent(expectedDecoded, dataContent.Data.ToArray()); + Assert.Equal($"data:application/zip;base64,{Base64Payload}", dataContent.Uri); + Assert.Same(block, dataContent.RawRepresentation); } [Fact] @@ -773,10 +772,10 @@ public void ConvertContentBlock_ResourceLinkBlock_WithUri_ShouldReturnUriContent AIContent result = DefaultMcpToolHandler.ConvertContentBlock(block); // Assert - UriContent uriContent = result.Should().BeOfType().Subject; - uriContent.Uri.ToString().Should().Be("https://example.com/resource.bin"); - uriContent.MediaType.Should().Be("application/zip"); - uriContent.RawRepresentation.Should().BeSameAs(block); + UriContent uriContent = Assert.IsType(result); + Assert.Equal("https://example.com/resource.bin", uriContent.Uri.ToString()); + Assert.Equal("application/zip", uriContent.MediaType); + Assert.Same(block, uriContent.RawRepresentation); } [Fact] @@ -794,9 +793,9 @@ public void ConvertContentBlock_ResourceLinkBlock_WithNullMimeType_ShouldDefault AIContent result = DefaultMcpToolHandler.ConvertContentBlock(block); // Assert - UriContent uriContent = result.Should().BeOfType().Subject; - uriContent.Uri.ToString().Should().Be("https://example.com/resource"); - uriContent.MediaType.Should().Be("application/octet-stream"); + UriContent uriContent = Assert.IsType(result); + Assert.Equal("https://example.com/resource", uriContent.Uri.ToString()); + Assert.Equal("application/octet-stream", uriContent.MediaType); } [Fact] @@ -819,11 +818,11 @@ public void ConvertContentBlock_ResourceLinkBlock_WithMeta_ShouldPropagateToAddi AIContent result = DefaultMcpToolHandler.ConvertContentBlock(block); // Assert - UriContent uriContent = result.Should().BeOfType().Subject; - uriContent.AdditionalProperties.Should().NotBeNull(); - uriContent.AdditionalProperties!.Should().HaveCount(2); - uriContent.AdditionalProperties["traceId"].Should().BeSameAs(block.Meta!["traceId"]); - uriContent.AdditionalProperties["priority"].Should().BeSameAs(block.Meta["priority"]); + UriContent uriContent = Assert.IsType(result); + Assert.NotNull(uriContent.AdditionalProperties); + Assert.Equal(2, uriContent.AdditionalProperties.Count); + Assert.Same(block.Meta!["traceId"], uriContent.AdditionalProperties["traceId"]); + Assert.Same(block.Meta["priority"], uriContent.AdditionalProperties["priority"]); } [Fact] @@ -841,9 +840,9 @@ public void ConvertContentBlock_ResourceLinkBlock_WithName_ShouldMapNameToFilena AIContent result = DefaultMcpToolHandler.ConvertContentBlock(block); // Assert - UriContent uriContent = result.Should().BeOfType().Subject; - uriContent.AdditionalProperties.Should().NotBeNull(); - uriContent.AdditionalProperties!["filename"].Should().Be("resource.bin"); + UriContent uriContent = Assert.IsType(result); + Assert.NotNull(uriContent.AdditionalProperties); + Assert.Equal("resource.bin", uriContent.AdditionalProperties!["filename"]); } #pragma warning disable MCP9005 // Verify compatibility mapping for deprecated sampling content blocks. @@ -863,12 +862,12 @@ public void ConvertContentBlock_ToolUseContentBlock_ShouldReturnFunctionCallCont AIContent result = DefaultMcpToolHandler.ConvertContentBlock(block); // Assert - FunctionCallContent call = result.Should().BeOfType().Subject; - call.CallId.Should().Be("call-1"); - call.Name.Should().Be("get_weather"); - call.Arguments.Should().NotBeNull(); - call.Arguments!.Should().ContainKey("city"); - call.RawRepresentation.Should().BeSameAs(block); + FunctionCallContent call = Assert.IsType(result); + Assert.Equal("call-1", call.CallId); + Assert.Equal("get_weather", call.Name); + Assert.NotNull(call.Arguments); + Assert.Contains("city", call.Arguments!); + Assert.Same(block, call.RawRepresentation); } [Fact] @@ -886,10 +885,10 @@ public void ConvertContentBlock_ToolResultContentBlock_NotError_ShouldReturnFunc AIContent result = DefaultMcpToolHandler.ConvertContentBlock(block); // Assert - FunctionResultContent functionResult = result.Should().BeOfType().Subject; - functionResult.CallId.Should().Be("call-1"); - functionResult.Exception.Should().BeNull(); - functionResult.RawRepresentation.Should().BeSameAs(block); + FunctionResultContent functionResult = Assert.IsType(result); + Assert.Equal("call-1", functionResult.CallId); + Assert.Null(functionResult.Exception); + Assert.Same(block, functionResult.RawRepresentation); } [Fact] @@ -907,10 +906,10 @@ public void ConvertContentBlock_ToolResultContentBlock_WithIsError_ShouldSetExce AIContent result = DefaultMcpToolHandler.ConvertContentBlock(block); // Assert - FunctionResultContent functionResult = result.Should().BeOfType().Subject; - functionResult.CallId.Should().Be("call-2"); - functionResult.Exception.Should().NotBeNull(); - functionResult.RawRepresentation.Should().BeSameAs(block); + FunctionResultContent functionResult = Assert.IsType(result); + Assert.Equal("call-2", functionResult.CallId); + Assert.NotNull(functionResult.Exception); + Assert.Same(block, functionResult.RawRepresentation); } #pragma warning restore MCP9005 @@ -932,9 +931,9 @@ public void ConvertContentBlock_BlockWithMeta_ShouldPropagateToAdditionalPropert AIContent result = DefaultMcpToolHandler.ConvertContentBlock(block); // Assert - result.AdditionalProperties.Should().NotBeNull(); - result.AdditionalProperties!.Should().ContainKey("traceId"); - result.AdditionalProperties.Should().ContainKey("priority"); + Assert.NotNull(result.AdditionalProperties); + Assert.True(result.AdditionalProperties!.ContainsKey("traceId")); + Assert.True(result.AdditionalProperties.ContainsKey("priority")); } #endregion @@ -952,7 +951,7 @@ public void StripCredentialHeadersOnCrossOrigin_SameOrigin_RetainsAuthorization( OriginPinningHandler.StripCredentialHeadersOnCrossOrigin(request, new Uri("https://trusted.example.com")); // Assert — same origin, credential is preserved - request.Headers.Contains("Authorization").Should().BeTrue(); + Assert.True(request.Headers.Contains("Authorization")); } [Fact] @@ -966,7 +965,7 @@ public void StripCredentialHeadersOnCrossOrigin_DifferentHost_RemovesAuthorizati OriginPinningHandler.StripCredentialHeadersOnCrossOrigin(request, new Uri("https://trusted.example.com")); // Assert — credential must not cross the origin boundary - request.Headers.Contains("Authorization").Should().BeFalse(); + Assert.False(request.Headers.Contains("Authorization")); } [Fact] @@ -980,7 +979,7 @@ public void StripCredentialHeadersOnCrossOrigin_DifferentPort_RemovesAuthorizati OriginPinningHandler.StripCredentialHeadersOnCrossOrigin(request, new Uri("https://trusted.example.com")); // Assert - request.Headers.Contains("Authorization").Should().BeFalse(); + Assert.False(request.Headers.Contains("Authorization")); } [Fact] @@ -995,7 +994,7 @@ public void StripCredentialHeadersOnCrossOrigin_ExplicitDefaultPort_RetainsAutho OriginPinningHandler.StripCredentialHeadersOnCrossOrigin(request, new Uri("https://trusted.example.com:443/mcp")); // Assert — explicit vs implicit default port is the same origin - request.Headers.Contains("Authorization").Should().BeTrue(); + Assert.True(request.Headers.Contains("Authorization")); } [Fact] @@ -1010,8 +1009,8 @@ public void StripCredentialHeadersOnCrossOrigin_RelativeRequestUri_RetainsAuthor Action act = () => OriginPinningHandler.StripCredentialHeadersOnCrossOrigin(request, new Uri("https://trusted.example.com")); // Assert — does not throw and leaves the credential in place - act.Should().NotThrow(); - request.Headers.Contains("Authorization").Should().BeTrue(); + Assert.Null(Record.Exception(act)); + Assert.True(request.Headers.Contains("Authorization")); } [Fact] @@ -1025,7 +1024,7 @@ public void StripCredentialHeadersOnCrossOrigin_DifferentScheme_RemovesAuthoriza OriginPinningHandler.StripCredentialHeadersOnCrossOrigin(request, new Uri("https://trusted.example.com")); // Assert - request.Headers.Contains("Authorization").Should().BeFalse(); + Assert.False(request.Headers.Contains("Authorization")); } [Fact] @@ -1041,9 +1040,9 @@ public void StripCredentialHeadersOnCrossOrigin_CrossOrigin_RemovesCookieAndProx OriginPinningHandler.StripCredentialHeadersOnCrossOrigin(request, new Uri("https://trusted.example.com")); // Assert — all credential-bearing headers are stripped - request.Headers.Contains("Authorization").Should().BeFalse(); - request.Headers.Contains("Cookie").Should().BeFalse(); - request.Headers.Contains("Proxy-Authorization").Should().BeFalse(); + Assert.False(request.Headers.Contains("Authorization")); + Assert.False(request.Headers.Contains("Cookie")); + Assert.False(request.Headers.Contains("Proxy-Authorization")); } [Fact] @@ -1058,8 +1057,8 @@ public void StripCredentialHeadersOnCrossOrigin_CrossOrigin_PreservesNonCredenti OriginPinningHandler.StripCredentialHeadersOnCrossOrigin(request, new Uri("https://trusted.example.com")); // Assert — only credential headers are removed; other headers are untouched - request.Headers.Contains("Authorization").Should().BeFalse(); - request.Headers.Contains("X-Trace-Id").Should().BeTrue(); + Assert.False(request.Headers.Contains("Authorization")); + Assert.True(request.Headers.Contains("X-Trace-Id")); } [Fact] @@ -1077,8 +1076,8 @@ public async Task OriginPinningHandler_CrossOriginRequest_DoesNotForwardAuthoriz using HttpResponseMessage response = await invoker.SendAsync(request, CancellationToken.None); // Assert — the credential never reached the inner handler for the foreign origin - response.StatusCode.Should().Be(System.Net.HttpStatusCode.OK); - inner.LastRequestHadAuthorization.Should().BeFalse(); + Assert.Equal(System.Net.HttpStatusCode.OK, response.StatusCode); + Assert.False(inner.LastRequestHadAuthorization); } [Fact] @@ -1096,8 +1095,8 @@ public async Task OriginPinningHandler_SameOriginRequest_ForwardsAuthorizationAs using HttpResponseMessage response = await invoker.SendAsync(request, CancellationToken.None); // Assert — same-origin credential flows through normally - response.StatusCode.Should().Be(System.Net.HttpStatusCode.OK); - inner.LastRequestHadAuthorization.Should().BeTrue(); + Assert.Equal(System.Net.HttpStatusCode.OK, response.StatusCode); + Assert.True(inner.LastRequestHadAuthorization); } private sealed class CapturingHandler : HttpMessageHandler diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.Mcp.UnitTests/Microsoft.Agents.AI.Workflows.Declarative.Mcp.UnitTests.csproj b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.Mcp.UnitTests/Microsoft.Agents.AI.Workflows.Declarative.Mcp.UnitTests.csproj index 057e2cd950c..51bcbf589bd 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.Mcp.UnitTests/Microsoft.Agents.AI.Workflows.Declarative.Mcp.UnitTests.csproj +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.Mcp.UnitTests/Microsoft.Agents.AI.Workflows.Declarative.Mcp.UnitTests.csproj @@ -9,7 +9,6 @@ - diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/DefaultHttpRequestHandlerTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/DefaultHttpRequestHandlerTests.cs index e679a89ca3d..2d9aaf7fe15 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/DefaultHttpRequestHandlerTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/DefaultHttpRequestHandlerTests.cs @@ -7,7 +7,6 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -using FluentAssertions; namespace Microsoft.Agents.AI.Workflows.Declarative.UnitTests; @@ -29,7 +28,7 @@ public async Task ConstructorWithNoParametersCreatesInstanceAsync() await using DefaultHttpRequestHandler handler = new(); // Assert - handler.Should().NotBeNull(); + Assert.NotNull(handler); } [Fact] @@ -39,7 +38,7 @@ public async Task ConstructorWithNullProviderCreatesInstanceAsync() await using DefaultHttpRequestHandler handler = new(httpClientProvider: null); // Assert - handler.Should().NotBeNull(); + Assert.NotNull(handler); } [Fact] @@ -49,7 +48,7 @@ public void ConstructorWithNullHttpClientThrows() Action act = () => _ = new DefaultHttpRequestHandler((HttpClient)null!); // Assert - act.Should().Throw(); + Assert.Throws(act); } [Fact] @@ -69,9 +68,9 @@ public async Task ConstructorWithHttpClientUsesSuppliedClientForAllRequestsAsync HttpRequestResult result = await handler.SendAsync(request); // Assert - the supplied HttpClient's underlying handler saw the request - messageHandler.LastRequest.Should().NotBeNull(); - messageHandler.LastRequest!.RequestUri!.ToString().Should().Be(TestUrl); - result.Body.Should().Be("ok"); + Assert.NotNull(messageHandler.LastRequest); + Assert.Equal(TestUrl, messageHandler.LastRequest!.RequestUri!.ToString()); + Assert.Equal("ok", result.Body); } [Fact] @@ -88,7 +87,7 @@ public async Task DisposeAsyncDoesNotDisposeCallerSuppliedHttpClientAsync() // Assert - supplied client remains usable (not disposed) Func act = async () => await suppliedClient.GetAsync(new Uri(TestUrl)); - await act.Should().NotThrowAsync(); + Assert.IsNotType(await Record.ExceptionAsync(act)); } #endregion @@ -105,7 +104,7 @@ public async Task SendAsyncWithNullRequestThrowsAsync() Func act = async () => await handler.SendAsync(null!); // Assert - await act.Should().ThrowAsync(); + await Assert.ThrowsAsync(act); } [Fact] @@ -119,7 +118,7 @@ public async Task SendAsyncWithEmptyUrlThrowsAsync() Func act = async () => await handler.SendAsync(request); // Assert - await act.Should().ThrowAsync(); + await Assert.ThrowsAsync(act); } [Fact] @@ -133,7 +132,7 @@ public async Task SendAsyncWithEmptyMethodThrowsAsync() Func act = async () => await handler.SendAsync(request); // Assert - await act.Should().ThrowAsync(); + await Assert.ThrowsAsync(act); } #endregion @@ -158,12 +157,12 @@ public async Task SendAsyncUsesProvidedHttpClientAsync() HttpRequestResult result = await handler.SendAsync(request); // Assert - messageHandler.LastRequest.Should().NotBeNull(); - messageHandler.LastRequest!.Method.Should().Be(HttpMethod.Get); - messageHandler.LastRequest.RequestUri!.ToString().Should().Be(TestUrl); - result.StatusCode.Should().Be(200); - result.IsSuccessStatusCode.Should().BeTrue(); - result.Body.Should().Be("hello"); + Assert.NotNull(messageHandler.LastRequest); + Assert.Equal(HttpMethod.Get, messageHandler.LastRequest!.Method); + Assert.Equal(TestUrl, messageHandler.LastRequest.RequestUri!.ToString()); + Assert.Equal(200, result.StatusCode); + Assert.True(result.IsSuccessStatusCode); + Assert.Equal("hello", result.Body); } [Fact] @@ -183,7 +182,7 @@ public async Task SendAsyncMapsAllKnownMethodsAsync() await handler.SendAsync(request); // Assert - messageHandler.LastRequest!.Method.Method.Should().Be(method); + Assert.Equal(method, messageHandler.LastRequest!.Method.Method); } } @@ -200,7 +199,7 @@ public async Task SendAsyncNormalizesWhitespaceAroundCustomMethodAsync() await handler.SendAsync(request); // Assert - fallback path should apply the same Trim/ToUpperInvariant normalization. - messageHandler.LastRequest!.Method.Method.Should().Be("CUSTOM"); + Assert.Equal("CUSTOM", messageHandler.LastRequest!.Method.Method); } [Fact] @@ -224,8 +223,8 @@ public async Task SendAsyncAppliesBodyAndContentTypeAsync() await handler.SendAsync(request); // Assert - messageHandler.LastRequestBody.Should().Be("{\"hello\":\"world\"}"); - messageHandler.LastRequestContentType.Should().Be("application/json"); + Assert.Equal("{\"hello\":\"world\"}", messageHandler.LastRequestBody); + Assert.Equal("application/json", messageHandler.LastRequestContentType); } [Fact] @@ -252,8 +251,8 @@ public async Task SendAsyncAppliesRequestHeadersAsync() await handler.SendAsync(request); // Assert - messageHandler.LastRequest!.Headers.Authorization!.ToString().Should().Be("Bearer secret"); - messageHandler.LastRequest.Headers.Accept.Should().Contain(mediaType => mediaType.MediaType == "application/json"); + Assert.Equal("Bearer secret", messageHandler.LastRequest!.Headers.Authorization!.ToString()); + Assert.Contains(messageHandler.LastRequest.Headers.Accept, mediaType => mediaType.MediaType == "application/json"); } [Fact] @@ -281,7 +280,7 @@ public async Task SendAsyncRoutesContentHeadersToBodyAsync() await handler.SendAsync(request); // Assert - messageHandler.LastRequest!.Content!.Headers.ContentLanguage.Should().Contain("en-US"); + Assert.Contains("en-US", messageHandler.LastRequest!.Content!.Headers.ContentLanguage); } [Fact] @@ -309,11 +308,11 @@ public async Task SendAsyncCapturesResponseHeadersAsync() HttpRequestResult result = await handler.SendAsync(request); // Assert - result.Headers.Should().NotBeNull(); - result.Headers!.Should().ContainKey("X-Request-Id"); - result.Headers!["Set-Cookie"].Should().BeEquivalentTo(s_setCookieValues); + Assert.NotNull(result.Headers); + Assert.Contains("X-Request-Id", result.Headers!); + Assert.Equivalent(s_setCookieValues, result.Headers!["Set-Cookie"]); // Content headers also flattened in. - result.Headers!.Should().ContainKey("Content-Type"); + Assert.Contains("Content-Type", result.Headers!); } [Fact] @@ -334,9 +333,9 @@ public async Task SendAsyncReturnsFailureStatusWithoutThrowingAsync() HttpRequestResult result = await handler.SendAsync(request); // Assert - result.IsSuccessStatusCode.Should().BeFalse(); - result.StatusCode.Should().Be(400); - result.Body.Should().Be("bad request"); + Assert.False(result.IsSuccessStatusCode); + Assert.Equal(400, result.StatusCode); + Assert.Equal("bad request", result.Body); } [Fact] @@ -362,7 +361,7 @@ public async Task SendAsyncTimeoutCancelsRequestAsync() Func act = async () => await handler.SendAsync(request); // Assert - await act.Should().ThrowAsync(); + await Assert.ThrowsAnyAsync(act); } [Fact] @@ -382,8 +381,8 @@ public async Task SendAsyncFallsBackToOwnedClientWhenProviderReturnsNullAsync() Func act = async () => await handler.SendAsync(request); // Assert - await act.Should().ThrowAsync(); - providerCallCount.Should().Be(1); + await Assert.ThrowsAnyAsync(act); + Assert.Equal(1, providerCallCount); } #endregion @@ -400,7 +399,7 @@ public async Task DisposeAsyncCompletesAsync() Func act = async () => await handler.DisposeAsync(); // Assert - await act.Should().NotThrowAsync(); + Assert.Null(await Record.ExceptionAsync(act)); } [Fact] @@ -414,7 +413,7 @@ public async Task DisposeAsyncCalledMultipleTimesSucceedsAsync() Func second = async () => await handler.DisposeAsync(); // Assert - await second.Should().NotThrowAsync(); + Assert.Null(await Record.ExceptionAsync(second)); } #endregion @@ -444,10 +443,10 @@ public async Task QueryParametersAreAppendedToUrlAsync() await handler.SendAsync(info); // Assert - fake.LastRequest.Should().NotBeNull(); + Assert.NotNull(fake.LastRequest); string? query = fake.LastRequest!.RequestUri!.Query; - query.Should().Contain("filter=active%20items"); - query.Should().Contain("ids=1%2C2%2C3"); + Assert.Contains("filter=active%20items", query); + Assert.Contains("ids=1%2C2%2C3", query); } [Fact] @@ -472,7 +471,7 @@ public async Task QueryParametersPreserveExistingQueryStringAsync() await handler.SendAsync(info); // Assert - fake.LastRequest!.RequestUri!.Query.Should().Be("?existing=yes&added=true"); + Assert.Equal("?existing=yes&added=true", fake.LastRequest!.RequestUri!.Query); } #endregion diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Kit/PortableValuePredicateTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Kit/PortableValuePredicateTests.cs index 4ed50afb5ad..9c4058835d4 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Kit/PortableValuePredicateTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Kit/PortableValuePredicateTests.cs @@ -1,7 +1,4 @@ -// Copyright (c) Microsoft. All rights reserved. - -using FluentAssertions; -using Microsoft.Agents.AI.Workflows.Declarative.Events; +using Microsoft.Agents.AI.Workflows.Declarative.Events; using Microsoft.Agents.AI.Workflows.Declarative.Kit; using Microsoft.Agents.AI.Workflows.Declarative.ObjectModel; @@ -25,7 +22,7 @@ public void ActionExecutorResult_ThrowIfNot_WithDirectActionExecutorResult_Retur ActionExecutorResult actual = ActionExecutorResult.ThrowIfNot(result); // Assert - actual.Should().BeSameAs(result); + Assert.Same(result, actual); } [Fact] @@ -39,7 +36,7 @@ public void ActionExecutorResult_ThrowIfNot_WithPortableValueWrappedActionExecut ActionExecutorResult actual = ActionExecutorResult.ThrowIfNot(wrapped); // Assert - actual.ExecutorId.Should().Be("test-executor"); + Assert.Equal("test-executor", actual.ExecutorId); } [Fact] @@ -80,7 +77,7 @@ public void InvokeAzureAgentExecutor_RequiresInput_WithDirectExternalInputReques ExternalInputRequest request = new("test prompt"); // Act & Assert - InvokeAzureAgentExecutor.RequiresInput(request).Should().BeTrue(); + Assert.True(InvokeAzureAgentExecutor.RequiresInput(request)); } [Fact] @@ -91,7 +88,7 @@ public void InvokeAzureAgentExecutor_RequiresInput_WithPortableValueWrappedReque PortableValue wrapped = new(request); // Act & Assert - InvokeAzureAgentExecutor.RequiresInput(wrapped).Should().BeTrue(); + Assert.True(InvokeAzureAgentExecutor.RequiresInput(wrapped)); } [Fact] @@ -101,7 +98,7 @@ public void InvokeAzureAgentExecutor_RequiresInput_WithActionExecutorResult_Retu ActionExecutorResult result = new("test"); // Act & Assert - InvokeAzureAgentExecutor.RequiresInput(result).Should().BeFalse(); + Assert.False(InvokeAzureAgentExecutor.RequiresInput(result)); } [Fact] @@ -111,7 +108,7 @@ public void InvokeAzureAgentExecutor_RequiresNothing_WithDirectActionExecutorRes ActionExecutorResult result = new("test"); // Act & Assert - InvokeAzureAgentExecutor.RequiresNothing(result).Should().BeTrue(); + Assert.True(InvokeAzureAgentExecutor.RequiresNothing(result)); } [Fact] @@ -122,7 +119,7 @@ public void InvokeAzureAgentExecutor_RequiresNothing_WithPortableValueWrappedRes PortableValue wrapped = new(result); // Act & Assert - InvokeAzureAgentExecutor.RequiresNothing(wrapped).Should().BeTrue(); + Assert.True(InvokeAzureAgentExecutor.RequiresNothing(wrapped)); } [Fact] @@ -132,7 +129,7 @@ public void InvokeAzureAgentExecutor_RequiresNothing_WithExternalInputRequest_Re ExternalInputRequest request = new("test prompt"); // Act & Assert - InvokeAzureAgentExecutor.RequiresNothing(request).Should().BeFalse(); + Assert.False(InvokeAzureAgentExecutor.RequiresNothing(request)); } #endregion @@ -147,7 +144,7 @@ public void InvokeMcpToolExecutor_RequiresInput_WithPortableValueWrappedRequest_ PortableValue wrapped = new(request); // Act & Assert - InvokeMcpToolExecutor.RequiresInput(wrapped).Should().BeTrue(); + Assert.True(InvokeMcpToolExecutor.RequiresInput(wrapped)); } [Fact] @@ -158,7 +155,7 @@ public void InvokeMcpToolExecutor_RequiresNothing_WithPortableValueWrappedResult PortableValue wrapped = new(result); // Act & Assert - InvokeMcpToolExecutor.RequiresNothing(wrapped).Should().BeTrue(); + Assert.True(InvokeMcpToolExecutor.RequiresNothing(wrapped)); } #endregion @@ -173,7 +170,7 @@ public void QuestionExecutor_IsComplete_WithPortableValueWrappedResult_NullResul PortableValue wrapped = new(result); // Act & Assert - QuestionExecutor.IsComplete(wrapped).Should().BeTrue(); + Assert.True(QuestionExecutor.IsComplete(wrapped)); } [Fact] @@ -184,7 +181,7 @@ public void QuestionExecutor_IsComplete_WithPortableValueWrappedResult_NonNullRe PortableValue wrapped = new(result); // Act & Assert - QuestionExecutor.IsComplete(wrapped).Should().BeFalse(); + Assert.False(QuestionExecutor.IsComplete(wrapped)); } #endregion diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests.csproj b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests.csproj index 57d4858dbbe..470a7fca49b 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests.csproj +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests.csproj @@ -12,7 +12,6 @@ - diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.Generators.UnitTests/ExecutorRouteGeneratorTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.Generators.UnitTests/ExecutorRouteGeneratorTests.cs index 15fb2429c67..d3154c27cfc 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.Generators.UnitTests/ExecutorRouteGeneratorTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.Generators.UnitTests/ExecutorRouteGeneratorTests.cs @@ -2,7 +2,6 @@ using System; using System.Linq; -using FluentAssertions; namespace Microsoft.Agents.AI.Workflows.Generators.UnitTests; @@ -36,11 +35,11 @@ private void HandleMessage(string message, IWorkflowContext context) var result = GeneratorTestHelper.RunGenerator(source); - result.RunResult.GeneratedTrees.Should().HaveCount(1); + Assert.Single(result.RunResult.GeneratedTrees); var generated = result.RunResult.GeneratedTrees[0]; - generated.Should().AddHandler("this.HandleMessage", "string"); + SyntaxTreeAssert.AddHandler(generated, "this.HandleMessage", "string"); } [Fact] @@ -67,10 +66,10 @@ private ValueTask HandleMessageAsync(string message, IWorkflowContext context) var result = GeneratorTestHelper.RunGenerator(source); - result.RunResult.GeneratedTrees.Should().HaveCount(1); + Assert.Single(result.RunResult.GeneratedTrees); var generated = result.RunResult.GeneratedTrees[0].ToString(); - generated.Should().Contain(".AddHandler(this.HandleMessageAsync)"); + Assert.Contains(".AddHandler(this.HandleMessageAsync)", generated); } [Fact] @@ -97,10 +96,10 @@ private ValueTask HandleMessageAsync(string message, IWorkflowContext conte var result = GeneratorTestHelper.RunGenerator(source); - result.RunResult.GeneratedTrees.Should().HaveCount(1); + Assert.Single(result.RunResult.GeneratedTrees); var generated = result.RunResult.GeneratedTrees[0].ToString(); - generated.Should().Contain(".AddHandler(this.HandleMessageAsync)"); + Assert.Contains(".AddHandler(this.HandleMessageAsync)", generated); } [Fact] @@ -127,10 +126,10 @@ private ValueTask HandleMessageAsync(string message, IWorkflowContext context, C var result = GeneratorTestHelper.RunGenerator(source); - result.RunResult.GeneratedTrees.Should().HaveCount(1); + Assert.Single(result.RunResult.GeneratedTrees); var generated = result.RunResult.GeneratedTrees[0].ToString(); - generated.Should().Contain(".AddHandler(this.HandleMessageAsync)"); + Assert.Contains(".AddHandler(this.HandleMessageAsync)", generated); } #endregion @@ -167,12 +166,12 @@ private ValueTask HandleDoubleAsync(double message, IWorkflowContext con var result = GeneratorTestHelper.RunGenerator(source); - result.RunResult.GeneratedTrees.Should().HaveCount(1); + Assert.Single(result.RunResult.GeneratedTrees); var generated = result.RunResult.GeneratedTrees[0].ToString(); - generated.Should().Contain(".AddHandler(this.HandleString)"); - generated.Should().Contain(".AddHandler(this.HandleInt)"); - generated.Should().Contain(".AddHandler(this.HandleDoubleAsync)"); + Assert.Contains(".AddHandler(this.HandleString)", generated); + Assert.Contains(".AddHandler(this.HandleInt)", generated); + Assert.Contains(".AddHandler(this.HandleDoubleAsync)", generated); } #endregion @@ -203,11 +202,11 @@ private void HandleMessage(string message, IWorkflowContext context) { } var result = GeneratorTestHelper.RunGenerator(source); - result.RunResult.GeneratedTrees.Should().HaveCount(1); + Assert.Single(result.RunResult.GeneratedTrees); var generated = result.RunResult.GeneratedTrees[0]; - generated.Should().RegisterYieldedOutputType("global::TestNamespace.OutputMessage"); + SyntaxTreeAssert.RegisterYieldedOutputType(generated, "global::TestNamespace.OutputMessage"); } [Fact] @@ -234,10 +233,10 @@ private void HandleMessage(string message, IWorkflowContext context) { } var result = GeneratorTestHelper.RunGenerator(source); - result.RunResult.GeneratedTrees.Should().HaveCount(1); + Assert.Single(result.RunResult.GeneratedTrees); var generated = result.RunResult.GeneratedTrees[0]; - generated.Should().RegisterSentMessageType("global::TestNamespace.SendMessage"); + SyntaxTreeAssert.RegisterSentMessageType(generated, "global::TestNamespace.SendMessage"); } [Fact] @@ -265,10 +264,10 @@ private void HandleMessage(string message, IWorkflowContext context) { } var result = GeneratorTestHelper.RunGenerator(source); - result.RunResult.GeneratedTrees.Should().HaveCount(1); + Assert.Single(result.RunResult.GeneratedTrees); var generated = result.RunResult.GeneratedTrees[0]; - generated.Should().RegisterSentMessageType("global::TestNamespace.BroadcastMessage"); + SyntaxTreeAssert.RegisterSentMessageType(generated, "global::TestNamespace.BroadcastMessage"); } [Fact] @@ -296,10 +295,10 @@ private void HandleMessage(string message, IWorkflowContext context) { } var result = GeneratorTestHelper.RunGenerator(source); - result.RunResult.GeneratedTrees.Should().HaveCount(1); + Assert.Single(result.RunResult.GeneratedTrees); var generated = result.RunResult.GeneratedTrees[0]; - generated.Should().RegisterYieldedOutputType("global::TestNamespace.YieldedMessage"); + SyntaxTreeAssert.RegisterYieldedOutputType(generated, "global::TestNamespace.YieldedMessage"); } #endregion @@ -330,13 +329,13 @@ private void HandleMessage(string message, IWorkflowContext context) { } var result = GeneratorTestHelper.RunGenerator(source); - result.RunResult.GeneratedTrees.Should().HaveCount(1); - result.RunResult.Diagnostics.Should().BeEmpty(); + Assert.Single(result.RunResult.GeneratedTrees); + Assert.Empty(result.RunResult.Diagnostics); var generated = result.RunResult.GeneratedTrees[0]; - generated.Should().HaveHierarchy("OuterClass", "TestExecutor") - .And.AddHandler("this.HandleMessage", "string"); + SyntaxTreeAssert.HaveHierarchy(generated, "OuterClass", "TestExecutor"); + SyntaxTreeAssert.AddHandler(generated, "this.HandleMessage", "string"); } [Fact] @@ -366,13 +365,13 @@ private void HandleMessage(string message, IWorkflowContext context) { } var result = GeneratorTestHelper.RunGenerator(source); - result.RunResult.GeneratedTrees.Should().HaveCount(1); - result.RunResult.Diagnostics.Should().BeEmpty(); + Assert.Single(result.RunResult.GeneratedTrees); + Assert.Empty(result.RunResult.Diagnostics); var generated = result.RunResult.GeneratedTrees[0]; - generated.Should().HaveHierarchy("Outer", "Inner", "TestExecutor") - .And.AddHandler("this.HandleMessage", "string"); + SyntaxTreeAssert.HaveHierarchy(generated, "Outer", "Inner", "TestExecutor"); + SyntaxTreeAssert.AddHandler(generated, "this.HandleMessage", "string"); } [Fact] @@ -405,13 +404,13 @@ private void HandleMessage(int message, IWorkflowContext context) { } var result = GeneratorTestHelper.RunGenerator(source); - result.RunResult.GeneratedTrees.Should().HaveCount(1); - result.RunResult.Diagnostics.Should().BeEmpty(); + Assert.Single(result.RunResult.GeneratedTrees); + Assert.Empty(result.RunResult.Diagnostics); var generated = result.RunResult.GeneratedTrees[0]; - generated.Should().HaveHierarchy("Level1", "Level2", "Level3", "TestExecutor") - .And.AddHandler("this.HandleMessage", "int"); + SyntaxTreeAssert.HaveHierarchy(generated, "Level1", "Level2", "Level3", "TestExecutor"); + SyntaxTreeAssert.AddHandler(generated, "this.HandleMessage", "int"); } [Fact] @@ -436,14 +435,14 @@ private void HandleMessage(string message, IWorkflowContext context) { } var result = GeneratorTestHelper.RunGenerator(source); - result.RunResult.GeneratedTrees.Should().HaveCount(1); - result.RunResult.Diagnostics.Should().BeEmpty(); + Assert.Single(result.RunResult.GeneratedTrees); + Assert.Empty(result.RunResult.Diagnostics); var generated = result.RunResult.GeneratedTrees[0]; - generated.Should().NotHaveNamespace() - .And.HaveHierarchy("OuterClass", "TestExecutor") - .And.AddHandler("this.HandleMessage", "string"); + SyntaxTreeAssert.NotHaveNamespace(generated); + SyntaxTreeAssert.HaveHierarchy(generated, "OuterClass", "TestExecutor"); + SyntaxTreeAssert.AddHandler(generated, "this.HandleMessage", "string"); } [Fact] @@ -479,15 +478,14 @@ private ValueTask HandleMessage(int message, IWorkflowContext context) var result = GeneratorTestHelper.RunGenerator(source); // No generator diagnostics - result.RunResult.Diagnostics.Should().BeEmpty(); + Assert.Empty(result.RunResult.Diagnostics); // Check that the combined compilation (source + generated) has no errors var compilationDiagnostics = result.OutputCompilation.GetDiagnostics() .Where(d => d.Severity == CodeAnalysis.DiagnosticSeverity.Error) .ToList(); - compilationDiagnostics.Should().BeEmpty( - "generated code for nested classes should compile without errors"); + Assert.Empty(compilationDiagnostics ?? []); } [Fact] @@ -517,7 +515,7 @@ private void HandleMessage(string message, IWorkflowContext context) { } var result = GeneratorTestHelper.RunGenerator(source); - result.RunResult.GeneratedTrees.Should().HaveCount(1); + Assert.Single(result.RunResult.GeneratedTrees); var generated = result.RunResult.GeneratedTrees[0].ToString(); @@ -525,7 +523,7 @@ private void HandleMessage(string message, IWorkflowContext context) { } var openBraces = generated.Count(c => c == '{'); var closeBraces = generated.Count(c => c == '}'); - openBraces.Should().Be(closeBraces, "generated code should have balanced braces"); + Assert.Equal(closeBraces, openBraces); // For Outer.Inner.TestExecutor, we expect: // - 1 for Outer class @@ -533,7 +531,7 @@ private void HandleMessage(string message, IWorkflowContext context) { } // - 1 for TestExecutor class // - 1 for ConfigureProtocol method // = 4 pairs minimum - openBraces.Should().BeGreaterThanOrEqualTo(4, "should have braces for all nested classes and method"); + Assert.True((openBraces) >= (4)); } #endregion @@ -585,22 +583,21 @@ private ValueTask HandleIntAsync(int message, IWorkflowContext context) var result = GeneratorTestHelper.RunGenerator(file1, file2); // Should generate one file for the executor - result.RunResult.GeneratedTrees.Should().HaveCount(1); - result.RunResult.Diagnostics.Should().BeEmpty(); + Assert.Single(result.RunResult.GeneratedTrees); + Assert.Empty(result.RunResult.Diagnostics); var generated = result.RunResult.GeneratedTrees[0]; // Should have both handlers registered - generated.Should().AddHandler("this.HandleString", "string") - .And.AddHandler("this.HandleIntAsync", "int"); + SyntaxTreeAssert.AddHandler(generated, "this.HandleString", "string"); + SyntaxTreeAssert.AddHandler(generated, "this.HandleIntAsync", "int"); // Verify the generated code compiles with all three partials combined var compilationErrors = result.OutputCompilation.GetDiagnostics() .Where(d => d.Severity == CodeAnalysis.DiagnosticSeverity.Error) .ToList(); - compilationErrors.Should().BeEmpty( - "generated partial should compile correctly with the other partial files"); + Assert.Empty(compilationErrors ?? []); } [Fact] @@ -640,14 +637,14 @@ private void HandleFromFile2(int message, IWorkflowContext context) { } var result = GeneratorTestHelper.RunGenerator(file1, file2); - result.RunResult.GeneratedTrees.Should().HaveCount(1); - result.RunResult.Diagnostics.Should().BeEmpty(); + Assert.Single(result.RunResult.GeneratedTrees); + Assert.Empty(result.RunResult.Diagnostics); var generated = result.RunResult.GeneratedTrees[0]; // Both handlers from different files should be registered - generated.Should().AddHandler("this.HandleFromFile1", "string") - .And.AddHandler("this.HandleFromFile2", "int"); + SyntaxTreeAssert.AddHandler(generated, "this.HandleFromFile1", "string"); + SyntaxTreeAssert.AddHandler(generated, "this.HandleFromFile2", "int"); } [Fact] @@ -691,16 +688,16 @@ private void HandleFromFile2(int message, IWorkflowContext context) { } var result = GeneratorTestHelper.RunGenerator(file1, file2); - result.RunResult.GeneratedTrees.Should().HaveCount(1); - result.RunResult.Diagnostics.Should().BeEmpty(); + Assert.Single(result.RunResult.GeneratedTrees); + Assert.Empty(result.RunResult.Diagnostics); var generated = result.RunResult.GeneratedTrees[0]; // Verify SendsMessage and YieldsOutput from both partials are combined correctly - generated.Should().RegisterSentMessageType("string") - .And.RegisterSentMessageType("int") - .And.RegisterYieldedOutputType("string") - .And.RegisterYieldedOutputType("int"); + SyntaxTreeAssert.RegisterSentMessageType(generated, "string"); + SyntaxTreeAssert.RegisterSentMessageType(generated, "int"); + SyntaxTreeAssert.RegisterYieldedOutputType(generated, "string"); + SyntaxTreeAssert.RegisterYieldedOutputType(generated, "int"); } #endregion @@ -729,11 +726,10 @@ private void HandleMessage(string message, IWorkflowContext context) { } var result = GeneratorTestHelper.RunGenerator(source); // Should produce MAFGENWF003 diagnostic - result.RunResult.Diagnostics.Should().Contain(d => d.Id == "MAFGENWF003"); + Assert.Contains(result.RunResult.Diagnostics, d => d.Id == "MAFGENWF003"); // Should NOT generate any source (to avoid CS0260) - result.RunResult.GeneratedTrees.Should().BeEmpty( - "non-partial classes should not have source generated to avoid CS0260 compiler error"); + Assert.Empty(result.RunResult.GeneratedTrees); } [Fact] @@ -755,7 +751,7 @@ private void HandleMessage(string message, IWorkflowContext context) { } var result = GeneratorTestHelper.RunGenerator(source); - result.RunResult.Diagnostics.Should().Contain(d => d.Id == "MAFGENWF004"); + Assert.Contains(result.RunResult.Diagnostics, d => d.Id == "MAFGENWF004"); } [Fact] @@ -779,7 +775,7 @@ private static void HandleMessage(string message, IWorkflowContext context) { } var result = GeneratorTestHelper.RunGenerator(source); - result.RunResult.Diagnostics.Should().Contain(d => d.Id == "MAFGENWF007"); + Assert.Contains(result.RunResult.Diagnostics, d => d.Id == "MAFGENWF007"); } [Fact] @@ -803,7 +799,7 @@ private void HandleMessage(string message) { } var result = GeneratorTestHelper.RunGenerator(source); - result.RunResult.Diagnostics.Should().Contain(d => d.Id == "MAFGENWF005"); + Assert.Contains(result.RunResult.Diagnostics, d => d.Id == "MAFGENWF005"); } [Fact] @@ -827,7 +823,7 @@ private void HandleMessage(string message, string notContext) { } var result = GeneratorTestHelper.RunGenerator(source); - result.RunResult.Diagnostics.Should().Contain(d => d.Id == "MAFGENWF001"); + Assert.Contains(result.RunResult.Diagnostics, d => d.Id == "MAFGENWF001"); } #endregion @@ -861,8 +857,8 @@ private void HandleMessage(string message, IWorkflowContext context) { } var result = GeneratorTestHelper.RunGenerator(source); // Should produce diagnostic but not generate code - result.RunResult.Diagnostics.Should().Contain(d => d.Id == "MAFGENWF006"); - result.RunResult.GeneratedTrees.Should().BeEmpty(); + Assert.Contains(result.RunResult.Diagnostics, d => d.Id == "MAFGENWF006"); + Assert.Empty(result.RunResult.GeneratedTrees); } [Fact] @@ -885,7 +881,7 @@ private void SomeOtherMethod(string message, IWorkflowContext context) { } var result = GeneratorTestHelper.RunGenerator(source); - result.RunResult.GeneratedTrees.Should().BeEmpty(); + Assert.Empty(result.RunResult.GeneratedTrees); } #endregion @@ -918,13 +914,13 @@ public TestExecutor() : base("test") { } var result = GeneratorTestHelper.RunGenerator(source); - result.RunResult.GeneratedTrees.Should().HaveCount(1); + Assert.Single(result.RunResult.GeneratedTrees); var generated = result.RunResult.GeneratedTrees[0]; - generated.Should().RegisterSentMessageType("global::TestNamespace.MessageA") - .And.RegisterSentMessageType("global::TestNamespace.MessageB") - .And.RegisterSentMessageType("global::TestNamespace.MessageC"); + SyntaxTreeAssert.RegisterSentMessageType(generated, "global::TestNamespace.MessageA"); + SyntaxTreeAssert.RegisterSentMessageType(generated, "global::TestNamespace.MessageB"); + SyntaxTreeAssert.RegisterSentMessageType(generated, "global::TestNamespace.MessageC"); } [Theory] @@ -951,12 +947,11 @@ public TestExecutor() : base("test") { } var result = GeneratorTestHelper.RunGenerator(source); - result.RunResult.Diagnostics.Should().ContainSingle(); + Assert.Single(result.RunResult.Diagnostics); var diagnostic = result.RunResult.Diagnostics.Single(); - diagnostic.Id.Should().Be("MAFGENWF008"); - diagnostic.GetMessage().Should().Be( - "Class 'TestExecutor' uses [SendsMessage] or [YieldsOutput] but is not declared as partial"); - result.RunResult.GeneratedTrees.Should().BeEmpty(); + Assert.Equal("MAFGENWF008", diagnostic.Id); + Assert.Equal("Class 'TestExecutor' uses [SendsMessage] or [YieldsOutput] but is not declared as partial", diagnostic.GetMessage()); + Assert.Empty(result.RunResult.GeneratedTrees); } [Fact] @@ -988,12 +983,11 @@ internal sealed record ReduceComplete(string FilePath); var result = GeneratorTestHelper.RunGenerator(source); - result.RunResult.Diagnostics.Should().ContainSingle(); + Assert.Single(result.RunResult.Diagnostics); var diagnostic = result.RunResult.Diagnostics.Single(); - diagnostic.Id.Should().Be("MAFGENWF008"); - diagnostic.GetMessage().Should().Be( - "Class 'CompletionExecutor' uses [SendsMessage] or [YieldsOutput] but is not declared as partial"); - result.RunResult.GeneratedTrees.Should().BeEmpty(); + Assert.Equal("MAFGENWF008", diagnostic.Id); + Assert.Equal("Class 'CompletionExecutor' uses [SendsMessage] or [YieldsOutput] but is not declared as partial", diagnostic.GetMessage()); + Assert.Empty(result.RunResult.GeneratedTrees); } [Theory] @@ -1019,12 +1013,11 @@ public partial class NotAnExecutor var result = GeneratorTestHelper.RunGenerator(source); - result.RunResult.Diagnostics.Should().ContainSingle(); + Assert.Single(result.RunResult.Diagnostics); var diagnostic = result.RunResult.Diagnostics.Single(); - diagnostic.Id.Should().Be("MAFGENWF009"); - diagnostic.GetMessage().Should().Be( - "Class 'NotAnExecutor' uses [SendsMessage] or [YieldsOutput] but does not derive from Executor"); - result.RunResult.GeneratedTrees.Should().BeEmpty(); + Assert.Equal("MAFGENWF009", diagnostic.Id); + Assert.Equal("Class 'NotAnExecutor' uses [SendsMessage] or [YieldsOutput] but does not derive from Executor", diagnostic.GetMessage()); + Assert.Empty(result.RunResult.GeneratedTrees); } [Fact] @@ -1052,15 +1045,15 @@ public TestExecutor() : base("test") { } var result = GeneratorTestHelper.RunGenerator(source); - result.RunResult.GeneratedTrees.Should().HaveCount(1); - result.RunResult.Diagnostics.Should().BeEmpty(); + Assert.Single(result.RunResult.GeneratedTrees); + Assert.Empty(result.RunResult.Diagnostics); var generated = result.RunResult.GeneratedTrees[0]; // Verify partial declarations are present - generated.Should().HaveHierarchy("OuterClass", "TestExecutor") + SyntaxTreeAssert.HaveHierarchy(generated, "OuterClass", "TestExecutor"); // Verify protocol types are generated - .And.RegisterSentMessageType("global::TestNamespace.BroadcastMessage"); + SyntaxTreeAssert.RegisterSentMessageType(generated, "global::TestNamespace.BroadcastMessage"); } [Fact] @@ -1085,12 +1078,12 @@ public GenericExecutor() : base("generic") { } var result = GeneratorTestHelper.RunGenerator(source); - result.RunResult.GeneratedTrees.Should().HaveCount(1); + Assert.Single(result.RunResult.GeneratedTrees); var generated = result.RunResult.GeneratedTrees[0]; - generated.Should().HaveHierarchy("GenericExecutor") - .And.RegisterSentMessageType("global::TestNamespace.BroadcastMessage"); + SyntaxTreeAssert.HaveHierarchy(generated, "GenericExecutor"); + SyntaxTreeAssert.RegisterSentMessageType(generated, "global::TestNamespace.BroadcastMessage"); } [Fact] @@ -1123,17 +1116,16 @@ public override System.Threading.Tasks.ValueTask HandleAsync(string message, IWo var result = GeneratorTestHelper.RunGenerator(source); - result.RunResult.GeneratedTrees.Should().HaveCount(1); - result.RunResult.Diagnostics.Should().BeEmpty(); + Assert.Single(result.RunResult.GeneratedTrees); + Assert.Empty(result.RunResult.Diagnostics); var generated = result.RunResult.GeneratedTrees[0].ToString(); // Base class Executor overrides ConfigureProtocol, so the generated override // must chain to base to preserve the inherited handler registration. - generated.Should().Contain("return base.ConfigureProtocol(protocolBuilder)", - because: "Executor overrides ConfigureProtocol, so base must be called to preserve its handler registration"); - generated.Should().Contain(".SendsMessage()"); - generated.Should().Contain(".YieldsOutput()"); + Assert.Contains("return base.ConfigureProtocol(protocolBuilder)", generated); + Assert.Contains(".SendsMessage()", generated); + Assert.Contains(".YieldsOutput()", generated); } [Fact] @@ -1161,15 +1153,14 @@ public BroadcastExecutor() : base("broadcast") { } var result = GeneratorTestHelper.RunGenerator(source); - result.RunResult.GeneratedTrees.Should().HaveCount(1); - result.RunResult.Diagnostics.Should().BeEmpty(); + Assert.Single(result.RunResult.GeneratedTrees); + Assert.Empty(result.RunResult.Diagnostics); var generated = result.RunResult.GeneratedTrees[0].ToString(); // Executor's ConfigureProtocol is abstract — no base call needed. - generated.Should().Contain("return protocolBuilder", - because: "Executor base class has no non-abstract ConfigureProtocol, so no base call is needed"); - generated.Should().NotContain("base.ConfigureProtocol"); + Assert.Contains("return protocolBuilder", generated); + Assert.DoesNotContain("base.ConfigureProtocol", generated); } #endregion @@ -1197,12 +1188,12 @@ private void HandleMessage(T message, IWorkflowContext context) { } var result = GeneratorTestHelper.RunGenerator(source); - result.RunResult.GeneratedTrees.Should().HaveCount(1); + Assert.Single(result.RunResult.GeneratedTrees); var generated = result.RunResult.GeneratedTrees[0]; - generated.Should().HaveHierarchy("GenericExecutor") - .And.AddHandler("this.HandleMessage", "T"); + SyntaxTreeAssert.HaveHierarchy(generated, "GenericExecutor"); + SyntaxTreeAssert.AddHandler(generated, "this.HandleMessage", "T"); } #endregion diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.Generators.UnitTests/GeneratorTestHelper.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.Generators.UnitTests/GeneratorTestHelper.cs index f631fc85515..a063c332ded 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.Generators.UnitTests/GeneratorTestHelper.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.Generators.UnitTests/GeneratorTestHelper.cs @@ -8,7 +8,6 @@ using System.Reflection; using System.Threading; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -60,10 +59,10 @@ public static void AssertGeneratesSource(string source, string expectedGenerated { var result = RunGenerator(source); - result.RunResult.GeneratedTrees.Should().HaveCount(1, "expected exactly one generated file"); + Assert.Single(result.RunResult.GeneratedTrees); var generatedSource = result.RunResult.GeneratedTrees[0].ToString(); - generatedSource.Should().Contain(expectedGeneratedSource); + Assert.Contains(expectedGeneratedSource, generatedSource); } /// @@ -72,7 +71,7 @@ public static void AssertGeneratesSource(string source, string expectedGenerated public static void AssertGeneratesNoSource(string source) { var result = RunGenerator(source); - result.RunResult.GeneratedTrees.Should().BeEmpty("expected no generated files"); + Assert.Empty(result.RunResult.GeneratedTrees); } /// @@ -83,8 +82,7 @@ public static void AssertProducesDiagnostic(string source, string diagnosticId) var result = RunGenerator(source); var generatorDiagnostics = result.RunResult.Diagnostics; - generatorDiagnostics.Should().Contain(d => d.Id == diagnosticId, - $"expected diagnostic {diagnosticId} to be produced"); + Assert.Contains(generatorDiagnostics, d => d.Id == diagnosticId); } /// @@ -98,7 +96,7 @@ public static void AssertCompilationSucceeds(string source) .Where(d => d.Severity == DiagnosticSeverity.Error) .ToList(); - errors.Should().BeEmpty("compilation should succeed without errors"); + Assert.Empty(errors ?? []); } private static ImmutableArray GetMetadataReferences() diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.Generators.UnitTests/Microsoft.Agents.AI.Workflows.Generators.UnitTests.csproj b/dotnet/tests/Microsoft.Agents.AI.Workflows.Generators.UnitTests/Microsoft.Agents.AI.Workflows.Generators.UnitTests.csproj index 81b91bf17d3..4720e85695d 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.Generators.UnitTests/Microsoft.Agents.AI.Workflows.Generators.UnitTests.csproj +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.Generators.UnitTests/Microsoft.Agents.AI.Workflows.Generators.UnitTests.csproj @@ -16,7 +16,6 @@ - diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.Generators.UnitTests/SyntaxTreeFluentExtensions.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.Generators.UnitTests/SyntaxTreeFluentExtensions.cs index 3da1e7d8911..d564a7eb485 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.Generators.UnitTests/SyntaxTreeFluentExtensions.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.Generators.UnitTests/SyntaxTreeFluentExtensions.cs @@ -1,220 +1,137 @@ // Copyright (c) Microsoft. All rights reserved. using System; -using FluentAssertions; -using FluentAssertions.Execution; -using FluentAssertions.Primitives; using Microsoft.CodeAnalysis; namespace Microsoft.Agents.AI.Workflows.Generators.UnitTests; -internal sealed class SyntaxTreeAssertions : ObjectAssertions +internal static class SyntaxTreeAssert { - private readonly string _syntaxString; - - public SyntaxTreeAssertions(SyntaxTree instance, AssertionChain assertionChain) : base(instance, assertionChain) - { - this._syntaxString = instance.ToString(); - } - - public AndConstraint AddHandler(string handlerName) + public static void AddHandler(SyntaxTree syntaxTree, string handlerName) { + string syntaxString = syntaxTree.ToString(); string expectedRegistration = $".AddHandler({handlerName})"; - this.CurrentAssertionChain - .ForCondition(this._syntaxString.Contains(expectedRegistration)) - .BecauseOf($"expected handler {handlerName} to be registered") - .FailWith("Expected {context} to contain handler registration {0}{reason}, but it was not found. Actual syntax: {1}", - expectedRegistration, this._syntaxString); - - return new(this); + Assert.Contains(expectedRegistration, syntaxString); } - public AndConstraint AddHandler(string handlerName, string inTypeParam) + public static void AddHandler(SyntaxTree syntaxTree, string handlerName, string inTypeParam) { + string syntaxString = syntaxTree.ToString(); string expectedRegistration = $".AddHandler<{inTypeParam}>({handlerName})"; - this.CurrentAssertionChain - .ForCondition(this._syntaxString.Contains(expectedRegistration)) - .BecauseOf($"expected handler {handlerName} to be registered") - .FailWith("Expected {context} to contain handler registration {0}{reason}, but it was not found. Actual syntax: {1}", - expectedRegistration, this._syntaxString); - - return new(this); + Assert.Contains(expectedRegistration, syntaxString); } - public AndConstraint AddHandler(string handlerName, string inTypeParam, string outTypeParam) + public static void AddHandler(SyntaxTree syntaxTree, string handlerName, string inTypeParam, string outTypeParam) { + string syntaxString = syntaxTree.ToString(); string expectedRegistration = $".AddHandler<{inTypeParam},{outTypeParam}>({handlerName})"; - this.CurrentAssertionChain - .ForCondition(this._syntaxString.Contains(expectedRegistration)) - .BecauseOf($"expected handler {handlerName} to be registered") - .FailWith("Expected {context} to contain handler registration {0}{reason}, but it was not found. Actual syntax: {1}", - expectedRegistration, this._syntaxString); - - return new(this); + Assert.Contains(expectedRegistration, syntaxString); } - public AndConstraint AddHandler(string handlerName, bool globalQualified = false) + public static void AddHandler(SyntaxTree syntaxTree, string handlerName, bool globalQualified = false) { Type inType = typeof(TIn); string inTypeParam = globalQualified ? $"global::{inType.FullName}" : inType.Name; - return this.AddHandler(handlerName, inTypeParam); + AddHandler(syntaxTree, handlerName, inTypeParam); } - public AndConstraint AddHandler(string handlerName, bool globalQualified = false) + public static void AddHandler(SyntaxTree syntaxTree, string handlerName, bool globalQualified = false) { Type inType = typeof(TIn), outType = typeof(TOut); string inTypeParam = globalQualified ? $"global::{inType.FullName}" : inType.Name; string outTypeParam = globalQualified ? $"global::{outType.FullName}" : outType.Name; - return this.AddHandler(handlerName, inTypeParam, outTypeParam); + AddHandler(syntaxTree, handlerName, inTypeParam, outTypeParam); } - public AndConstraint HaveNoHandlers() + public static void HaveNoHandlers(SyntaxTree syntaxTree) { - this.CurrentAssertionChain - .ForCondition(!this._syntaxString.Contains(".AddHandler(")) - .BecauseOf("expected no handlers to be registered") - .FailWith("Expected {context} to have no handler registrations{reason}, but found at least one. Actual syntax: {1}", - this._syntaxString); - - return new(this); + Assert.DoesNotContain(".AddHandler(", syntaxTree.ToString()); } - public AndConstraint RegisterSentMessageType(string messageTypeParam) + public static void RegisterSentMessageType(SyntaxTree syntaxTree, string messageTypeParam) { + string syntaxString = syntaxTree.ToString(); string expectedRegistration = $".SendsMessage<{messageTypeParam}>()"; - this.CurrentAssertionChain - .ForCondition(this._syntaxString.Contains(expectedRegistration)) - .BecauseOf($"expected message type {messageTypeParam} to be registered") - .FailWith("Expected {context} to contain message type registration {0}{reason}, but it was not found. Actual syntax: {1}", - expectedRegistration, this._syntaxString); - - return new(this); + Assert.Contains(expectedRegistration, syntaxString); } - public AndConstraint RegisterSentMessageType(bool globalQualified = true) + public static void RegisterSentMessageType(SyntaxTree syntaxTree, bool globalQualified = true) { Type messageType = typeof(TMessage); string messageTypeParam = globalQualified ? $"global::{messageType.FullName}" : messageType.Name; - return this.RegisterSentMessageType(messageTypeParam); + RegisterSentMessageType(syntaxTree, messageTypeParam); } - public AndConstraint NotRegisterSentMessageTypes() + public static void NotRegisterSentMessageTypes(SyntaxTree syntaxTree) { - this.CurrentAssertionChain - .ForCondition(!this._syntaxString.Contains(".SendsMessage<")) - .BecauseOf("expected no message types to be registered") - .FailWith("Expected {context} to have no message type registrations{reason}, but found at least one. Actual syntax: {1}", - this._syntaxString); - - return new(this); + Assert.DoesNotContain(".SendsMessage<", syntaxTree.ToString()); } - public AndConstraint RegisterYieldedOutputType(string outputTypeParam) + public static void RegisterYieldedOutputType(SyntaxTree syntaxTree, string outputTypeParam) { + string syntaxString = syntaxTree.ToString(); string expectedRegistration = $".YieldsOutput<{outputTypeParam}>()"; - this.CurrentAssertionChain - .ForCondition(this._syntaxString.Contains(expectedRegistration)) - .BecauseOf($"expected output type {outputTypeParam} to be registered") - .FailWith("Expected {context} to contain output type registration {0}{reason}, but it was not found. Actual syntax: {1}", - expectedRegistration, this._syntaxString); - - return new(this); + Assert.Contains(expectedRegistration, syntaxString); } - public AndConstraint RegisterYieldedOutputType(bool globalQualified = true) + public static void RegisterYieldedOutputType(SyntaxTree syntaxTree, bool globalQualified = true) { Type outputType = typeof(TOutput); string outputTypeParam = globalQualified ? $"global::{outputType.FullName}" : outputType.Name; - return this.RegisterYieldedOutputType(outputTypeParam); + RegisterYieldedOutputType(syntaxTree, outputTypeParam); } - public AndConstraint NotRegisterYieldedOutputTypes() + public static void NotRegisterYieldedOutputTypes(SyntaxTree syntaxTree) { - this.CurrentAssertionChain - .ForCondition(!this._syntaxString.Contains(".YieldsOutput<")) - .BecauseOf("expected no output types to be registered") - .FailWith("Expected {context} to have no output type registrations{reason}, but found at least one. Actual syntax: {1}", - this._syntaxString); - - return new(this); + Assert.DoesNotContain(".YieldsOutput<", syntaxTree.ToString()); } - private AndConstraint ContainPartialDeclaration(int level, int index, string className) + private static void ContainPartialDeclaration(int level, int index, string className) { - this.CurrentAssertionChain - .ForCondition(index > 0) - .BecauseOf($"expected \"partial class {className}\" at nesting level {level}") - .FailWith("Expected {context} to contain \"partial class {0}\" at nesting level {1}{reason}, but it was not found. Actual syntax: {2}", - className, level, this._syntaxString); - - return new(this); + Assert.True(index > 0, $"Expected to contain \"partial class {className}\" at nesting level {level}."); } - private AndConstraint DeclarePartialsInCorrectOrder(int prevIndex, int currIndex, string prevClass, string currClass) + private static void DeclarePartialsInCorrectOrder(int prevIndex, int currIndex, string prevClass, string currClass) { - this.CurrentAssertionChain - .ForCondition(prevIndex < currIndex) - .BecauseOf($"expected \"partial class {prevClass}\" before \"partial class {currClass}\"") - .FailWith("Expected {context} to have \"partial class {0}\" before \"partial class {1}\"{reason}, but the order was incorrect. Actual syntax: {2}", - prevClass, currClass, this._syntaxString); - - return new(this); + Assert.True(prevIndex < currIndex, $"Expected \"partial class {prevClass}\" before \"partial class {currClass}\"."); } - public AndConstraint HaveHierarchy(params string[] expectedNesting) + public static void HaveHierarchy(SyntaxTree syntaxTree, params string[] expectedNesting) { if (expectedNesting.Length == 0) { - return new AndConstraint(this); + return; } + string syntaxString = syntaxTree.ToString(); int[] indicies = new int[expectedNesting.Length]; for (int i = 0; i < expectedNesting.Length; i++) { - indicies[i] = this._syntaxString.IndexOf($"partial class {expectedNesting[i]}", StringComparison.Ordinal); + indicies[i] = syntaxString.IndexOf($"partial class {expectedNesting[i]}", StringComparison.Ordinal); } // Verify partial declarations are present - AndConstraint runningResult = this.ContainPartialDeclaration(0, indicies[0], expectedNesting[0]); + ContainPartialDeclaration(0, indicies[0], expectedNesting[0]); for (int i = 1; i < expectedNesting.Length; i++) { - runningResult = runningResult.And.ContainPartialDeclaration(i, indicies[i], expectedNesting[i]) - .And.DeclarePartialsInCorrectOrder(indicies[i - 1], indicies[i], expectedNesting[i - 1], expectedNesting[i]); + ContainPartialDeclaration(i, indicies[i], expectedNesting[i]); + DeclarePartialsInCorrectOrder(indicies[i - 1], indicies[i], expectedNesting[i - 1], expectedNesting[i]); } - - return runningResult; } - public AndConstraint HaveNamespace() + public static void HaveNamespace(SyntaxTree syntaxTree) { - this.CurrentAssertionChain - .ForCondition(this._syntaxString.Contains("namespace ")) - .BecauseOf("expected namespace declaration") - .FailWith("Expected {context} to contain a namespace declaration{reason}, but it was found. Actual syntax: {0}", - this._syntaxString); - - return new(this); + Assert.Contains("namespace ", syntaxTree.ToString()); } - public AndConstraint NotHaveNamespace() + public static void NotHaveNamespace(SyntaxTree syntaxTree) { - this.CurrentAssertionChain - .ForCondition(!this._syntaxString.Contains("namespace ")) - .BecauseOf("expected no namespace declaration") - .FailWith("Expected {context} to not contain a namespace declaration{reason}, but it was found. Actual syntax: {0}", - this._syntaxString); - - return new(this); + Assert.DoesNotContain("namespace ", syntaxTree.ToString()); } } - -internal static class SyntaxTreeFluentExtensions -{ - public static SyntaxTreeAssertions Should(this SyntaxTree syntaxTree) => new(syntaxTree, AssertionChain.GetOrCreate()); -} diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AIAgentHostExecutorTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AIAgentHostExecutorTests.cs index e5c45ebe4e0..74a8474e65e 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AIAgentHostExecutorTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AIAgentHostExecutorTests.cs @@ -7,7 +7,6 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.Agents.AI.Workflows.Execution; using Microsoft.Agents.AI.Workflows.Specialized; using Microsoft.Extensions.AI; @@ -87,17 +86,17 @@ public async Task Test_AgentHostExecutor_AssignsStableMessageIdToContentfulStrea // Assert AgentResponseUpdateEvent[] updateEvents = testContext.Events.OfType().ToArray(); - updateEvents.Should().HaveCount(3); - updateEvents[0].Update.MessageId.Should().BeEmpty(); + Assert.Equal(3, updateEvents.Length); + Assert.True(string.IsNullOrEmpty(updateEvents[0].Update.MessageId)); string? messageId = updateEvents[1].Update.MessageId; - messageId.Should().NotBeNullOrEmpty(); - updateEvents.Skip(1).Should().OnlyContain(updateEvent => updateEvent.Update.MessageId == messageId); + Assert.False(string.IsNullOrEmpty(messageId)); + Assert.All(updateEvents.Skip(1), updateEvent => Assert.True(updateEvent.Update.MessageId == messageId)); - AgentResponseEvent responseEvent = testContext.Events.OfType().Should().ContainSingle().Subject; - ChatMessage responseMessage = responseEvent.Response.Messages.Should().ContainSingle().Subject; - responseMessage.MessageId.Should().Be(messageId); - responseMessage.Text.Should().Be("hello world"); + AgentResponseEvent responseEvent = Assert.Single(testContext.Events.OfType()); + ChatMessage responseMessage = Assert.Single(responseEvent.Response.Messages); + Assert.Equal(messageId, responseMessage.MessageId); + Assert.Equal("hello world", responseMessage.Text); } private static ChatMessage UserMessage => new(ChatRole.User, "Hello from User!") { AuthorName = "User" }; @@ -187,11 +186,11 @@ public async Task Test_AgentHostExecutor_ReassignsRolesIFFConfiguredAsync(bool e if (shouldThrow) { - await act.Should().ThrowAsync(); + await Assert.ThrowsAsync(act); } else { - await act.Should().NotThrowAsync(); + Assert.Null(await Record.ExceptionAsync(act)); } } @@ -250,8 +249,7 @@ public async Task Test_AgentHostExecutor_InterceptsRequestsIFFConfiguredAsync(bo List ExtractAndValidateRequestContents() where TRequest : AIContent { - IEnumerable requests = testContext.QueuedMessages.Should().ContainKey(executor.Id) - .WhoseValue + IEnumerable requests = Assert.Contains(executor.Id, testContext.QueuedMessages) .Select(envelope => envelope.Message as TRequest) .Where(item => item is not null) .Select(item => item!); @@ -272,10 +270,12 @@ List ExtractAndValidateRequestContents() where TRequest : AICo // Assert 2 // Since we are not finished, we expect the agent to not have produced a final response (="Remaining: 1") - AgentResponseEvent lastResponseEvent = testContext.Events.OfType().Should().NotBeEmpty() - .And.Subject.Last(); + List agentResponseEvents = testContext.Events.OfType().ToList(); + agentResponseEvents = testContext.Events.OfType().ToList(); + Assert.NotEmpty(agentResponseEvents); + AgentResponseEvent lastResponseEvent = agentResponseEvents.Last(); - lastResponseEvent.Response.Text.Should().Be("Remaining: 1"); + Assert.Equal("Remaining: 1", lastResponseEvent.Response.Text); // Act 3 object finalResponse = responses.Last(); @@ -283,10 +283,11 @@ List ExtractAndValidateRequestContents() where TRequest : AICo // Assert 3 // Now that we are finished, we expect the agent to have produced a final response - lastResponseEvent = testContext.Events.OfType().Should().NotBeEmpty() - .And.Subject.Last(); + agentResponseEvents = testContext.Events.OfType().ToList(); + Assert.NotEmpty(agentResponseEvents); + lastResponseEvent = agentResponseEvents.Last(); - lastResponseEvent.Response.Text.Should().Be("Done"); + Assert.Equal("Done", lastResponseEvent.Response.Text); } #region FilterForwardableMessages tests @@ -379,7 +380,7 @@ public async Task Test_AgentHostExecutor_FiltersNonPortableContentFromForwardedM await executor.TakeTurnAsync(new(), testContext.BindWorkflowContext(executor.Id)); // Assert: only the text message should be forwarded - testContext.QueuedMessages.Should().ContainKey(executor.Id); + Assert.Contains(executor.Id, testContext.QueuedMessages); List sentEnvelopes = testContext.QueuedMessages[executor.Id]; // Extract forwarded ChatMessage lists (filter out TurnToken) @@ -389,11 +390,10 @@ public async Task Test_AgentHostExecutor_FiltersNonPortableContentFromForwardedM .SelectMany(list => list) .ToList(); - forwardedMessages.Should().HaveCount(1); - forwardedMessages[0].Role.Should().Be(ChatRole.Assistant); - forwardedMessages[0].Contents.Should().HaveCount(1); - forwardedMessages[0].Contents[0].Should().BeOfType(); - ((TextContent)forwardedMessages[0].Contents[0]).Text.Should().Be("Useful response text"); + Assert.Single(forwardedMessages); + Assert.Equal(ChatRole.Assistant, forwardedMessages[0].Role); + TextContent content = Assert.IsType(Assert.Single(forwardedMessages[0].Contents)); + Assert.Equal("Useful response text", content.Text); } [Fact] @@ -425,9 +425,9 @@ public async Task Test_AgentHostExecutor_StripsRawRepresentationFromForwardedMes .SelectMany(list => list) .ToList(); - forwardedMessages.Should().HaveCount(1); - forwardedMessages[0].RawRepresentation.Should().BeNull(); - forwardedMessages[0].AuthorName.Should().Be(TestAgentName); + Assert.Single(forwardedMessages); + Assert.Null(forwardedMessages[0].RawRepresentation); + Assert.Equal(TestAgentName, forwardedMessages[0].AuthorName); } [Fact] @@ -464,12 +464,12 @@ public async Task Test_AgentHostExecutor_PreservesForwardableContentInMixedMessa .SelectMany(list => list) .ToList(); - forwardedMessages.Should().HaveCount(1); + Assert.Single(forwardedMessages); ChatMessage forwarded = forwardedMessages[0]; - forwarded.Contents.Should().HaveCount(2); - forwarded.Contents[0].Should().BeOfType(); - forwarded.Contents[1].Should().BeOfType(); - forwarded.RawRepresentation.Should().BeNull(); + Assert.Equal(2, forwarded.Contents.Count); + Assert.IsType(forwarded.Contents[0]); + Assert.IsType(forwarded.Contents[1]); + Assert.Null(forwarded.RawRepresentation); } [Fact] @@ -505,7 +505,7 @@ public async Task Test_AgentHostExecutor_DropsMessageWithOnlyNonPortableContentA .SelectMany(list => list) .ToList(); - forwardedMessages.Should().BeEmpty(); + Assert.Empty(forwardedMessages ?? []); } #endregion diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AIAgentHostingExecutorTestsBase.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AIAgentHostingExecutorTestsBase.cs index 2285074ce32..62a0c0f955a 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AIAgentHostingExecutorTestsBase.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AIAgentHostingExecutorTestsBase.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; -using FluentAssertions; using Microsoft.Extensions.AI; namespace Microsoft.Agents.AI.Workflows.UnitTests; @@ -28,24 +27,24 @@ protected static void CheckResponseUpdateEventsAgainstTestMessages(AgentResponse // The way TestReplayAgent is set up, it will emit one update per non-empty AIContent List expectedUpdateContents = TestMessages.SelectMany(message => message.Contents).ToList(); - updates.Should().HaveCount(expectedUpdateContents.Count); + Assert.Equal(expectedUpdateContents.Count, updates.Length); for (int i = 0; i < updates.Length; i++) { AgentResponseUpdateEvent updateEvent = updates[i]; AIContent expectedUpdateContent = expectedUpdateContents[i]; - updateEvent.ExecutorId.Should().Be(expectedExecutorId); + Assert.Equal(expectedExecutorId, updateEvent.ExecutorId); AgentResponseUpdate update = updateEvent.Update; - update.AuthorName.Should().Be(TestAgentName); - update.AgentId.Should().Be(TestAgentId); - update.Contents.Should().HaveCount(1); - update.Contents[0].Should().BeEquivalentTo(expectedUpdateContent); + Assert.Equal(TestAgentName, update.AuthorName); + Assert.Equal(TestAgentId, update.AgentId); + AIContent updateContent = Assert.Single(update.Contents); + Assert.Equivalent(expectedUpdateContent, updateContent); } } else { - updates.Should().BeEmpty(); + Assert.Empty(updates); } } @@ -53,27 +52,27 @@ protected static void CheckResponseEventsAgainstTestMessages(AgentResponseEvent[ { if (expectingResponse) { - updates.Should().HaveCount(1); + Assert.Single(updates); AgentResponseEvent responseEvent = updates[0]; - responseEvent.ExecutorId.Should().Be(expectedExecutorId); + Assert.Equal(expectedExecutorId, responseEvent.ExecutorId); AgentResponse response = responseEvent.Response; - response.AgentId.Should().Be(TestAgentId); - response.Messages.Should().HaveCount(TestMessages.Count - 1); + Assert.Equal(TestAgentId, response.AgentId); + Assert.Equal(TestMessages.Count - 1, response.Messages.Count); for (int i = 0; i < response.Messages.Count; i++) { ChatMessage responseMessage = response.Messages[i]; ChatMessage expectedMessage = TestMessages[i + 1]; // Skip the first empty message - responseMessage.AuthorName.Should().Be(TestAgentName); - responseMessage.Text.Should().Be(expectedMessage.Text); + Assert.Equal(TestAgentName, responseMessage.AuthorName); + Assert.Equal(expectedMessage.Text, responseMessage.Text); } } else { - updates.Should().BeEmpty(); + Assert.Empty(updates); } } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AgentEventsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AgentEventsTests.cs index 2b8c4805d15..19463c17371 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AgentEventsTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AgentEventsTests.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.Extensions.AI; namespace Microsoft.Agents.AI.Workflows.UnitTests; @@ -113,19 +112,19 @@ public async Task StreamingRun_WorkflowStartedEvent_ShouldBeEmittedBefore_SuperS } // Assert - events.Should().NotBeEmpty(); + Assert.NotEmpty(events); List startedEvents = events.OfType().ToList(); - startedEvents.Should().NotBeEmpty(); + Assert.NotEmpty(startedEvents); WorkflowStartedEvent? firstStartedEvent = startedEvents.FirstOrDefault(); SuperStepStartedEvent? firstSuperStepEvent = events.OfType().FirstOrDefault(); - firstSuperStepEvent.Should().NotBeNull(); + Assert.NotNull(firstSuperStepEvent); int startedIndex = events.IndexOf(firstStartedEvent!); int superStepIndex = events.IndexOf(firstSuperStepEvent!); - startedIndex.Should().BeLessThan(superStepIndex); + Assert.True((startedIndex) < (superStepIndex)); } /// @@ -150,9 +149,9 @@ public async Task StreamingRun_LockstepExecution_ShouldEmit_WorkflowStartedEvent } // Assert - events.Should().NotBeEmpty(); + Assert.NotEmpty(events); List startedEvents = events.OfType().ToList(); - startedEvents.Should().NotBeEmpty(); + Assert.NotEmpty(startedEvents); } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AgentWorkflowBuilderTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AgentWorkflowBuilderTests.cs index 4f74ab12c70..f0b25120df0 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AgentWorkflowBuilderTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AgentWorkflowBuilderTests.cs @@ -6,7 +6,6 @@ using System.Runtime.CompilerServices; using System.Text.RegularExpressions; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.Extensions.AI; #pragma warning disable SYSLIB1045 // Use GeneratedRegex @@ -61,9 +60,9 @@ public async Task Test_AgentWorkflowBuilder_BuildSequential_ChainOnlyAgentRespon (_, List? result, _, _) = await OrchestrationTestHelpers.RunWorkflowAsync(workflow, [new ChatMessage(ChatRole.User, "abc")]); - result.Should().NotBeNull(); - result!.Should().ContainSingle(); - result[0].AuthorName.Should().Be("agent2"); + Assert.NotNull(result); + Assert.Single(result!); + Assert.Equal("agent2", result[0].AuthorName); } [Fact] @@ -73,7 +72,7 @@ public void Test_AgentWorkflowBuilder_BuildSequential_WithWorkflowNameSetsNameOn "static-sequential", new OrchestrationTestHelpers.DoubleEchoAgent("agent1")); - workflow.Name.Should().Be("static-sequential"); + Assert.Equal("static-sequential", workflow.Name); } [Fact] @@ -114,7 +113,7 @@ public void Test_AgentWorkflowBuilder_BuildConcurrent_WithWorkflowNameSetsNameOn "static-concurrent", [new OrchestrationTestHelpers.DoubleEchoAgent("agent1")]); - workflow.Name.Should().Be("static-concurrent"); + Assert.Equal("static-concurrent", workflow.Name); } [Fact] @@ -131,8 +130,9 @@ [new OrchestrationTestHelpers.DoubleEchoAgent("agent1")], (_, List? result, _, _) = await OrchestrationTestHelpers.RunWorkflowAsync(workflow, [new ChatMessage(ChatRole.User, "abc")]); - result.Should().NotBeNull().And.ContainSingle(); - result![0].Text.Should().Be("custom-aggregator-result"); + Assert.NotNull(result); + Assert.Single(result); + Assert.Equal("custom-aggregator-result", result![0].Text); } [Fact] @@ -149,7 +149,7 @@ public void Test_AgentWorkflowBuilder_CreateSequentialBuilderWith_ReturnsConfigu SequentialWorkflowBuilder builder = AgentWorkflowBuilder.CreateSequentialBuilderWith(agent); Workflow workflow = builder.WithName("via-factory").Build(); - workflow.Name.Should().Be("via-factory"); + Assert.Equal("via-factory", workflow.Name); } [Fact] @@ -166,7 +166,7 @@ public void Test_AgentWorkflowBuilder_CreateConcurrentBuilderWith_ReturnsConfigu ConcurrentWorkflowBuilder builder = AgentWorkflowBuilder.CreateConcurrentBuilderWith(agent); Workflow workflow = builder.WithName("via-factory").Build(); - workflow.Name.Should().Be("via-factory"); + Assert.Equal("via-factory", workflow.Name); } [Fact] diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AggregatingExecutorTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AggregatingExecutorTests.cs index fac76744e97..42817cc8ea6 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AggregatingExecutorTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AggregatingExecutorTests.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. using System.Threading.Tasks; -using FluentAssertions; namespace Microsoft.Agents.AI.Workflows.UnitTests; @@ -19,9 +18,9 @@ public async Task AggregatingExecutor_HandleAsync_AggregatesIncrementallyAsync() string? result2 = await executor.HandleAsync("b", context, default); string? result3 = await executor.HandleAsync("c", context, default); - result1.Should().Be("a"); - result2.Should().Be("a+b"); - result3.Should().Be("a+b+c"); + Assert.Equal("a", result1); + Assert.Equal("a+b", result2); + Assert.Equal("a+b+c", result3); } [Fact] @@ -38,7 +37,7 @@ public async Task AggregatingExecutor_HandleAsync_FirstCallReceivesNullAggregate TestWorkflowContext context = new(executor.Id); await executor.HandleAsync("hello", context, default); - receivedAggregate.Should().BeNull("the first invocation should receive a null aggregate for reference types"); + Assert.Null(receivedAggregate); } [Fact] @@ -50,14 +49,14 @@ public async Task AggregatingExecutor_HandleAsync_AggregatorReturningNullClearsS TestWorkflowContext context = new(executor.Id); string? result1 = await executor.HandleAsync("a", context, default); - result1.Should().Be("a"); + Assert.Equal("a", result1); string? result2 = await executor.HandleAsync("clear", context, default); - result2.Should().BeNull("the aggregator returned null to clear the state"); + Assert.Null(result2); // After clearing, the next call should receive null aggregate again string? result3 = await executor.HandleAsync("b", context, default); - result3.Should().Be("b", "the aggregate should restart from null after being cleared"); + Assert.Equal("b", result3); } [Fact] @@ -71,7 +70,7 @@ public async Task AggregatingExecutor_HandleAsync_PersistsStateBetweenCallsAsync for (int i = 1; i <= 5; i++) { string? result = await executor.HandleAsync("tick", context, default); - result.Should().Be($"{i}", "the aggregate should increment with each call"); + Assert.Equal($"{i}", result); } } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/BackwardsCompatibility/JsonCheckpointSerializationTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/BackwardsCompatibility/JsonCheckpointSerializationTests.cs index e0f2bfca7d5..d689da00bfa 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/BackwardsCompatibility/JsonCheckpointSerializationTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/BackwardsCompatibility/JsonCheckpointSerializationTests.cs @@ -1,8 +1,8 @@ // Copyright (c) Microsoft. All rights reserved. using System.Collections.Generic; +using System.Linq; using System.Text.Json; -using FluentAssertions; using Microsoft.Agents.AI.Workflows.Checkpointing; namespace Microsoft.Agents.AI.Workflows.UnitTests.BackwardsCompatibility; @@ -30,10 +30,10 @@ public void Test_WorkflowOutputEvent_SingleTagCtorPopulatesTags() { WorkflowOutputEvent evt = new(data: "hello", executorId: "e1", tag: OutputTag.Intermediate); - evt.ExecutorId.Should().Be("e1"); - evt.Tags.Should().BeEquivalentTo(new[] { OutputTag.Intermediate }); - evt.HasTag(OutputTag.Intermediate).Should().BeTrue(); - evt.IsIntermediate().Should().BeTrue(); + Assert.Equal("e1", evt.ExecutorId); + Assert.Equivalent(new[] { OutputTag.Intermediate }, evt.Tags); + Assert.True(evt.HasTag(OutputTag.Intermediate)); + Assert.True(evt.IsIntermediate()); } [Fact] @@ -41,8 +41,8 @@ public void Test_WorkflowOutputEvent_NoTagsCtorIsUntagged() { WorkflowOutputEvent evt = new(data: "hello", executorId: "e1"); - evt.Tags.Should().BeEmpty(); - evt.IsIntermediate().Should().BeFalse("an event with no tags is a terminal/regular output"); + Assert.Empty(evt.Tags ?? []); + Assert.False(evt.IsIntermediate()); } [Fact] @@ -52,10 +52,10 @@ public void Test_WorkflowOutputEvent_MultiTagCtorPreservesAllTags() WorkflowOutputEvent evt = new(data: "hello", executorId: "e1", tags: new[] { OutputTag.Intermediate, customTag }); - evt.Tags.Should().HaveCount(2); - evt.HasTag(OutputTag.Intermediate).Should().BeTrue(); - evt.HasTag(customTag).Should().BeTrue(); - evt.IsIntermediate().Should().BeTrue(); + Assert.Equal(2, evt.Tags.Count()); + Assert.True(evt.HasTag(OutputTag.Intermediate)); + Assert.True(evt.HasTag(customTag)); + Assert.True(evt.IsIntermediate()); } // ---------- WorkflowInfo.OutputExecutorIds shape ---------- @@ -81,10 +81,10 @@ public void Test_JsonCheckpoint_WorkflowOutputExecutorsReadsLegacyArrayShape() WorkflowInfo? info = JsonSerializer.Deserialize(LegacyJson, s_options); - info.Should().NotBeNull(); - info!.OutputExecutorIds.Should().HaveCount(2); - info.OutputExecutorIds["a"].Should().BeEmpty("legacy ids are untagged regular outputs"); - info.OutputExecutorIds["b"].Should().BeEmpty(); + Assert.NotNull(info); + Assert.Equal(2, info!.OutputExecutorIds.Count); + Assert.Empty(info.OutputExecutorIds["a"] ?? []); + Assert.Empty(info.OutputExecutorIds["b"] ?? []); } [Fact] @@ -102,20 +102,20 @@ public void Test_JsonCheckpoint_WorkflowOutputExecutorsWritesMapShape() WorkflowInfo? back = JsonSerializer.Deserialize(json, s_options); - back.Should().NotBeNull(); - back!.OutputExecutorIds.Should().HaveCount(2); - back.OutputExecutorIds["a"].Should().BeEmpty(); - back.OutputExecutorIds["b"].Should().BeEquivalentTo(new[] { OutputTag.Intermediate }); + Assert.NotNull(back); + Assert.Equal(2, back!.OutputExecutorIds.Count); + Assert.Empty(back.OutputExecutorIds["a"] ?? []); + Assert.Equivalent(new[] { OutputTag.Intermediate }, back.OutputExecutorIds["b"]); // The map shape is detectable in the serialized JSON: the property value starts with `{`, not `[`. int idx = json.IndexOf("\"outputExecutorIds\"", System.StringComparison.Ordinal); - idx.Should().BeGreaterThan(-1); + Assert.True((idx) > (-1)); int colon = json.IndexOf(':', idx); int firstNonSpace = colon + 1; while (firstNonSpace < json.Length && char.IsWhiteSpace(json[firstNonSpace])) { firstNonSpace++; } - json[firstNonSpace].Should().Be('{', "OutputExecutorIds is written in the new map shape"); + Assert.Equal('{', json[firstNonSpace]); } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/ChatForwardingExecutorTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/ChatForwardingExecutorTests.cs index 805128509ce..65728f5becc 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/ChatForwardingExecutorTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/ChatForwardingExecutorTests.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.Agents.AI.Workflows.Checkpointing; using Microsoft.Extensions.AI; @@ -54,7 +53,7 @@ private async Task RunForwardMessageTestAsync(Cha TestWorkflowContext testContext = new(executor.Id); object? callResult = await executor.ExecuteCoreAsync(message, new TypeId(typeof(TMessage)), testContext); - callResult.Should().BeNull(); // ChatForwardingExecutor's do not have a return type + Assert.Null(callResult); // ChatForwardingExecutor's do not have a return type return testContext; } @@ -68,7 +67,7 @@ public async Task Test_ChatForwardingExecutor_DoesNotForwardStringByDefaultAsync // Act Func> action = () => this.RunForwardMessageTestAsync(executor, TestMessageContent); - await action.Should().ThrowAsync(); + await Assert.ThrowsAsync(action); } [Theory] @@ -94,12 +93,12 @@ internal async Task Test_ChatForwardingExecutor_ForwardsStringIfConfiguredAsync( { TestWorkflowContext testContext = await action(); - testContext.SentMessages.Should().HaveCount(1) - .And.BeEquivalentTo([new ChatMessage(chatRole, TestMessageContent)]); + ChatMessage sentMessage = Assert.IsType(Assert.Single(testContext.SentMessages)); + Assert.Equivalent(new ChatMessage(chatRole, TestMessageContent), sentMessage); } else { - await action.Should().ThrowAsync(); + await Assert.ThrowsAsync(action); } } @@ -114,7 +113,7 @@ public async Task Test_ChatForwardingExecutor_ForwardsChatMessageUnmodifiedAsync TestWorkflowContext testContext = await this.RunForwardMessageTestAsync(executor, testMessage); // Assert - testContext.SentMessages.Should().ContainSingle(message => ReferenceEquals(message, testMessage)); + Assert.Single(testContext.SentMessages); } [Theory] @@ -134,7 +133,7 @@ TestWorkflowContext testContext : await this.RunForwardMessageTestAsync(executor, testMessages); // Assert - testContext.SentMessages.Should().ContainSingle(messages => ReferenceEquals(messages, testMessages)); + Assert.Single(testContext.SentMessages); } [Fact] @@ -149,7 +148,7 @@ public async Task Test_ChatForwardingExecutor_ForwardsChatMessageArrayUnchangedA TestWorkflowContext testContext = await this.RunForwardMessageTestAsync(executor, testMessages); // Assert - testContext.SentMessages.Should().ContainSingle(messages => ReferenceEquals(messages, testMessages)); + Assert.Single(testContext.SentMessages); } [Fact] @@ -164,8 +163,9 @@ public async Task Test_ChatForwardingExecutor_ForwardsMessageCollectionAsListAsy TestWorkflowContext testContext = await this.RunForwardMessageTestAsync(executor, testMessages); // Assert - testContext.SentMessages.Should().ContainSingle(messages => !ReferenceEquals(messages, testMessages)) - .And.Subject.Single().Should().BeEquivalentTo(testMessages); + IReadOnlyList forwardedMessages = + Assert.IsAssignableFrom>(Assert.Single(testContext.SentMessages, messages => !ReferenceEquals(messages, testMessages))); + Assert.Equivalent(testMessages, forwardedMessages); } [Theory] @@ -182,6 +182,6 @@ public async Task Test_ChatForwardingExecutor_ForwardsTurnTokenUnmodifiedAsync(b TestWorkflowContext testContext = await this.RunForwardMessageTestAsync(executor, testTurnToken); // Assert - testContext.SentMessages.Should().BeEquivalentTo([testTurnToken]); + Assert.Equal(testTurnToken, Assert.Single(testContext.SentMessages)); } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/ChatProtocolExecutorTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/ChatProtocolExecutorTests.cs index 0f2ab2e7342..1fbbed541ed 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/ChatProtocolExecutorTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/ChatProtocolExecutorTests.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.Agents.AI.Workflows.Checkpointing; using Microsoft.Extensions.AI; @@ -48,7 +47,7 @@ public void ChatProtocolExecutor_DescribedProtocol_IsChatProtocol() ProtocolDescriptor protocol = executor.DescribeProtocol(); // Act & Assert - protocol.Should().Match(protocol => protocol.IsChatProtocol()); + Assert.True(protocol.IsChatProtocol()); } [Fact] @@ -69,10 +68,10 @@ public async Task ChatProtocolExecutor_Handles_ListOfChatMessagesAsync() await executor.TakeTurnAsync(new TurnToken(emitEvents: false), context); // Assert - executor.ReceivedMessages.Should().HaveCount(2); - executor.ReceivedMessages[0].Text.Should().Be("Hello"); - executor.ReceivedMessages[1].Text.Should().Be("World"); - executor.TurnCount.Should().Be(1); + Assert.Equal(2, executor.ReceivedMessages.Count); + Assert.Equal("Hello", executor.ReceivedMessages[0].Text); + Assert.Equal("World", executor.ReceivedMessages[1].Text); + Assert.Equal(1, executor.TurnCount); } [Fact] @@ -94,11 +93,11 @@ public async Task ChatProtocolExecutor_Handles_ArrayOfChatMessagesAsync() await executor.TakeTurnAsync(new TurnToken(emitEvents: false), context); // Assert - executor.ReceivedMessages.Should().HaveCount(3); - executor.ReceivedMessages[0].Role.Should().Be(ChatRole.System); - executor.ReceivedMessages[1].Role.Should().Be(ChatRole.User); - executor.ReceivedMessages[2].Role.Should().Be(ChatRole.Assistant); - executor.TurnCount.Should().Be(1); + Assert.Equal(3, executor.ReceivedMessages.Count); + Assert.Equal(ChatRole.System, executor.ReceivedMessages[0].Role); + Assert.Equal(ChatRole.User, executor.ReceivedMessages[1].Role); + Assert.Equal(ChatRole.Assistant, executor.ReceivedMessages[2].Role); + Assert.Equal(1, executor.TurnCount); } [Fact] @@ -115,9 +114,9 @@ public async Task ChatProtocolExecutor_Handles_SingleChatMessageAsync() await executor.TakeTurnAsync(new TurnToken(emitEvents: false), context); // Assert - executor.ReceivedMessages.Should().HaveCount(1); - executor.ReceivedMessages[0].Text.Should().Be("Single message"); - executor.TurnCount.Should().Be(1); + Assert.Single(executor.ReceivedMessages); + Assert.Equal("Single message", executor.ReceivedMessages[0].Text); + Assert.Equal(1, executor.TurnCount); } [Fact] @@ -137,9 +136,9 @@ await executor.ExecuteCoreAsync(new List await executor.TakeTurnAsync(new TurnToken(emitEvents: false), context); - executor.ReceivedMessages.Should().HaveCount(4); - executor.ReceivedMessages.Select(m => m.Text).Should().Equal("Message 1", "Message 2", "Message 3", "Message 4"); - executor.TurnCount.Should().Be(1); + Assert.Equal(4, executor.ReceivedMessages.Count); + Assert.Equal("Message 1", executor.ReceivedMessages[0].Text); + Assert.Equal(1, executor.TurnCount); executor.ReceivedMessages.Clear(); @@ -150,9 +149,9 @@ await executor.ExecuteCoreAsync(new List }, new TypeId(typeof(List)), context); await executor.TakeTurnAsync(new TurnToken(emitEvents: false), context); - executor.ReceivedMessages.Should().HaveCount(1); - executor.ReceivedMessages[0].Text.Should().Be("Second batch"); - executor.TurnCount.Should().Be(2); + Assert.Single(executor.ReceivedMessages); + Assert.Equal("Second batch", executor.ReceivedMessages[0].Text); + Assert.Equal(2, executor.TurnCount); } [Fact] @@ -168,9 +167,9 @@ public async Task ChatProtocolExecutor_WithStringRole_ConvertsStringToMessageAsy await executor.ExecuteCoreAsync("String message", new TypeId(typeof(string)), context); await executor.TakeTurnAsync(new TurnToken(emitEvents: false), context); - executor.ReceivedMessages.Should().HaveCount(1); - executor.ReceivedMessages[0].Role.Should().Be(ChatRole.User); - executor.ReceivedMessages[0].Text.Should().Be("String message"); + Assert.Single(executor.ReceivedMessages); + Assert.Equal(ChatRole.User, executor.ReceivedMessages[0].Role); + Assert.Equal("String message", executor.ReceivedMessages[0].Text); } [Fact] @@ -183,8 +182,8 @@ public async Task ChatProtocolExecutor_EmptyCollection_HandledCorrectlyAsync() await executor.ExecuteCoreAsync(Array.Empty(), new TypeId(typeof(ChatMessage[])), context); await executor.TakeTurnAsync(new TurnToken(emitEvents: false), context); - executor.ReceivedMessages.Should().BeEmpty(); - executor.TurnCount.Should().Be(1); + Assert.Empty(executor.ReceivedMessages); + Assert.Equal(1, executor.TurnCount); } [Theory] @@ -201,8 +200,8 @@ public async Task ChatProtocolExecutor_RoutesCollectionTypesAsync(Type collectio await executor.ExecuteCoreAsync(messagesToSend, new TypeId(collectionType), context); await executor.TakeTurnAsync(new TurnToken(emitEvents: false), context); - executor.ReceivedMessages.Should().HaveCount(1); - executor.ReceivedMessages[0].Text.Should().Be("Test message"); + Assert.Single(executor.ReceivedMessages); + Assert.Equal("Test message", executor.ReceivedMessages[0].Text); } [Fact] @@ -214,15 +213,15 @@ public async Task ChatProtocolExecutor_MultipleTurns_EachTurnProcessesSeparately await executor.ExecuteCoreAsync(new List { new(ChatRole.User, "Turn 1") }, new TypeId(typeof(List)), context); await executor.TakeTurnAsync(new TurnToken(emitEvents: false), context); - executor.ReceivedMessages.Should().HaveCount(1); + Assert.Single(executor.ReceivedMessages); await executor.ExecuteCoreAsync(new ChatMessage(ChatRole.User, "Turn 2"), new TypeId(typeof(ChatMessage)), context); await executor.TakeTurnAsync(new TurnToken(emitEvents: false), context); - executor.ReceivedMessages.Should().HaveCount(2); - executor.ReceivedMessages[0].Text.Should().Be("Turn 1"); - executor.ReceivedMessages[1].Text.Should().Be("Turn 2"); - executor.TurnCount.Should().Be(2); + Assert.Equal(2, executor.ReceivedMessages.Count); + Assert.Equal("Turn 1", executor.ReceivedMessages[0].Text); + Assert.Equal("Turn 2", executor.ReceivedMessages[1].Text); + Assert.Equal(2, executor.TurnCount); } [Fact] @@ -236,8 +235,8 @@ public async Task ChatProtocolExecutor_InitialWorkflowMessages_RoutedCorrectlyAs await executor.ExecuteCoreAsync(initialMessages, new TypeId(typeof(List)), context); await executor.TakeTurnAsync(new TurnToken(emitEvents: false), context); - executor.ReceivedMessages.Should().NotBeEmpty(); - executor.ReceivedMessages.Should().HaveCount(1); - executor.ReceivedMessages[0].Text.Should().Be("Kick off the workflow"); + Assert.NotEmpty(executor.ReceivedMessages); + Assert.Single(executor.ReceivedMessages); + Assert.Equal("Kick off the workflow", executor.ReceivedMessages[0].Text); } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/CheckpointManagerLatestTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/CheckpointManagerLatestTests.cs index 2bc8b7393e8..4c38475986f 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/CheckpointManagerLatestTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/CheckpointManagerLatestTests.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Text.Json; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.Agents.AI.Workflows.Checkpointing; namespace Microsoft.Agents.AI.Workflows.UnitTests; @@ -38,7 +37,7 @@ public async Task GetLatestCheckpointAsync_FileStore_ReturnsLastCommittedAsync() CheckpointInfo? latest = await manager.GetLatestCheckpointAsync(SessionId); // Assert: the durable index preserves commit order, so the latest checkpoint is the last committed. - index.Should().Equal(committed, "the file-store index should be returned in commit order"); - latest.Should().Be(committed[^1]); + Assert.Equal(committed, index); + Assert.Equal(committed[^1], latest); } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/CheckpointParentTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/CheckpointParentTests.cs index 0ecf3c993f5..fa95c89d9e2 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/CheckpointParentTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/CheckpointParentTests.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.Agents.AI.Workflows.Checkpointing; using Microsoft.Agents.AI.Workflows.InProc; @@ -45,12 +44,12 @@ internal async Task Checkpoint_FirstCheckpoint_ShouldHaveNullParentAsync(Executi } // Assert: The first checkpoint should have been created and stored with a null parent. - checkpoints.Should().NotBeEmpty("at least one checkpoint should have been created"); + Assert.NotEmpty(checkpoints); CheckpointInfo firstCheckpoint = checkpoints[0]; Checkpoint storedFirst = await ((ICheckpointManager)checkpointManager) .LookupCheckpointAsync(firstCheckpoint.SessionId, firstCheckpoint); - storedFirst.Parent.Should().BeNull("the first checkpoint should have no parent"); + Assert.Null(storedFirst.Parent); } [Theory] @@ -90,22 +89,22 @@ internal async Task Checkpoint_SubsequentCheckpoints_ShouldChainParentsAsync(Exe } // Assert: We should have at least 3 checkpoints - checkpoints.Should().HaveCountGreaterThanOrEqualTo(3); + Assert.True(checkpoints.Count >= (3)); // Verify the parent chain Checkpoint stored0 = await ((ICheckpointManager)checkpointManager) .LookupCheckpointAsync(checkpoints[0].SessionId, checkpoints[0]); - stored0.Parent.Should().BeNull("the first checkpoint should have no parent"); + Assert.Null(stored0.Parent); Checkpoint stored1 = await ((ICheckpointManager)checkpointManager) .LookupCheckpointAsync(checkpoints[1].SessionId, checkpoints[1]); - stored1.Parent.Should().NotBeNull("the second checkpoint should have a parent"); - stored1.Parent.Should().Be(checkpoints[0], "the second checkpoint's parent should be the first checkpoint"); + Assert.NotNull(stored1.Parent); + Assert.Equal(checkpoints[0], stored1.Parent); Checkpoint stored2 = await ((ICheckpointManager)checkpointManager) .LookupCheckpointAsync(checkpoints[2].SessionId, checkpoints[2]); - stored2.Parent.Should().NotBeNull("the third checkpoint should have a parent"); - stored2.Parent.Should().Be(checkpoints[1], "the third checkpoint's parent should be the second checkpoint"); + Assert.NotNull(stored2.Parent); + Assert.Equal(checkpoints[1], stored2.Parent); } [Theory] @@ -142,7 +141,7 @@ internal async Task Checkpoint_AfterResume_ShouldHaveResumedCheckpointAsParentAs } } - firstRunCheckpoints.Should().HaveCountGreaterThanOrEqualTo(2); + Assert.True(firstRunCheckpoints.Count >= (2)); CheckpointInfo resumePoint = firstRunCheckpoints[0]; // Dispose the first run to release workflow ownership before resuming. @@ -166,10 +165,10 @@ internal async Task Checkpoint_AfterResume_ShouldHaveResumedCheckpointAsParentAs } // Assert: The first checkpoint after resume should have the resume point as its parent. - resumedCheckpoints.Should().NotBeEmpty(); + Assert.NotEmpty(resumedCheckpoints); Checkpoint storedResumed = await ((ICheckpointManager)checkpointManager) .LookupCheckpointAsync(resumedCheckpoints[0].SessionId, resumedCheckpoints[0]); - storedResumed.Parent.Should().NotBeNull("checkpoint created after resume should have a parent"); - storedResumed.Parent.Should().Be(resumePoint, "checkpoint after resume should reference the checkpoint we resumed from"); + Assert.NotNull(storedResumed.Parent); + Assert.Equal(resumePoint, storedResumed.Parent); } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/CheckpointResumeTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/CheckpointResumeTests.cs index e218c048c26..e1d7af741c6 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/CheckpointResumeTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/CheckpointResumeTests.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.Agents.AI.Workflows.InProc; using Microsoft.Agents.AI.Workflows.Sample; @@ -57,8 +56,8 @@ internal async Task Checkpoint_Resume_WithPendingRequests_RepublishesRequestInfo } } - originalRequests.Should().NotBeEmpty("the workflow should have created at least one external request"); - checkpoint.Should().NotBeNull("a checkpoint should have been created"); + Assert.NotEmpty(originalRequests); + Assert.NotNull(checkpoint); } // Act 2: Resume from the checkpoint. @@ -77,11 +76,8 @@ internal async Task Checkpoint_Resume_WithPendingRequests_RepublishesRequestInfo } } - reEmittedRequests.Should().HaveCount(originalRequests.Count, - "all pending requests from the checkpoint should be re-emitted after resume"); - reEmittedRequests.Select(r => r.RequestId) - .Should().BeEquivalentTo(originalRequests.Select(r => r.RequestId), - "the re-emitted request IDs should match the original pending request IDs"); + Assert.Equal(originalRequests.Count, reEmittedRequests.Count); + Assert.Equivalent(originalRequests.Select(r => r.RequestId), reEmittedRequests.Select(r => r.RequestId)); } /// @@ -118,7 +114,7 @@ internal async Task Checkpoint_Resume_WithPendingRequests_RunStatusIsPendingRequ } } - checkpoint.Should().NotBeNull(); + Assert.NotNull(checkpoint); } // Act: Resume from the checkpoint and consume events so the run loop processes. @@ -133,8 +129,7 @@ internal async Task Checkpoint_Resume_WithPendingRequests_RunStatusIsPendingRequ // Assert RunStatus status = await resumed.GetStatusAsync(); - status.Should().Be(RunStatus.PendingRequests, - "the resumed workflow should report PendingRequests after rehydration"); + Assert.Equal(RunStatus.PendingRequests, status); } /// @@ -177,8 +172,8 @@ internal async Task Checkpoint_Resume_RespondToPendingRequest_CompletesWithoutDu } } - pendingRequest.Should().NotBeNull(); - checkpoint.Should().NotBeNull(); + Assert.NotNull(pendingRequest); + Assert.NotNull(checkpoint); } // Act: Resume and respond to the restored request. @@ -195,20 +190,17 @@ internal async Task Checkpoint_Resume_RespondToPendingRequest_CompletesWithoutDu if (evt is RequestInfoEvent requestInfo) { requestEventCount++; - requestInfo.Request.RequestId.Should().Be(pendingRequest!.RequestId, - "the re-emitted request should match the original"); + Assert.Equal(pendingRequest!.RequestId, requestInfo.Request.RequestId); } } - requestEventCount.Should().Be(1, - "the pending request should be emitted exactly once (no duplicates)"); + Assert.Equal(1, requestEventCount); // Assert intermediate state before responding: the run should be in PendingRequests // and we should have observed the re-emitted request. If the first WatchStreamAsync // didn't complete or yielded nothing, these assertions catch it with a clear message. RunStatus statusBeforeResponse = await resumed.GetStatusAsync(); - statusBeforeResponse.Should().Be(RunStatus.PendingRequests, - "the run should be in PendingRequests state before we send a response"); + Assert.Equal(RunStatus.PendingRequests, statusBeforeResponse); // Now send the response and verify the workflow processes it. ExternalResponse response = pendingRequest!.CreateResponse("World"); @@ -223,10 +215,8 @@ internal async Task Checkpoint_Resume_RespondToPendingRequest_CompletesWithoutDu postResponseEvents.Add(evt); } - postResponseEvents.Should().NotBeEmpty( - "the workflow should process the response and produce events"); - postResponseEvents.OfType().Should().BeEmpty( - "no errors should occur when processing the restored request's response"); + Assert.NotEmpty(postResponseEvents); + Assert.Empty(postResponseEvents.OfType() ?? []); } /// @@ -252,11 +242,9 @@ internal async Task Checkpoint_Restore_WithPendingRequests_RepublishesRequestInf await run.SendResponseAsync(pendingRequest.CreateResponse("World")); List firstCompletionEvents = await ReadToHaltAsync(run); - firstCompletionEvents.OfType().Should().BeEmpty( - "the workflow should continue cleanly before we restore"); + Assert.Empty(firstCompletionEvents.OfType() ?? []); RunStatus statusAfterFirstResponse = await run.GetStatusAsync(); - statusAfterFirstResponse.Should().Be(RunStatus.Idle, - "the workflow should finish processing the first response before we restore"); + Assert.Equal(RunStatus.Idle, statusAfterFirstResponse); // Act await run.RestoreCheckpointAsync(checkpoint); @@ -265,18 +253,15 @@ internal async Task Checkpoint_Restore_WithPendingRequests_RepublishesRequestInf List restoredEvents = await ReadToHaltAsync(run); ExternalRequest[] replayedRequests = [.. restoredEvents.OfType().Select(evt => evt.Request)]; - replayedRequests.Should().ContainSingle("runtime restore should re-emit the restored pending request"); - replayedRequests[0].RequestId.Should().Be(pendingRequest.RequestId, - "the replayed request should match the request captured at the checkpoint"); + Assert.Single(replayedRequests); + Assert.Equal(pendingRequest.RequestId, replayedRequests[0].RequestId); await run.SendResponseAsync(replayedRequests[0].CreateResponse("Again")); List secondCompletionEvents = await ReadToHaltAsync(run); - secondCompletionEvents.OfType().Should().BeEmpty( - "runtime restore replay should not introduce workflow errors"); + Assert.Empty(secondCompletionEvents.OfType() ?? []); RunStatus statusAfterRestoreResponse = await run.GetStatusAsync(); - statusAfterRestoreResponse.Should().Be(RunStatus.Idle, - "the workflow should be able to continue after the runtime restore replay"); + Assert.Equal(RunStatus.Idle, statusAfterRestoreResponse); } /// @@ -299,26 +284,19 @@ internal async Task Checkpoint_Restore_ClearsQueuedExternalResponsesBeforeImport await run.RestoreCheckpointAsync(checkpoint); List restoredEvents = await ReadToHaltAsync(run); - ExternalRequest replayedRequest = restoredEvents.OfType() - .Select(evt => evt.Request) - .Should() - .ContainSingle("the restored run should still be waiting for the checkpointed request") - .Subject; - - restoredEvents.OfType().Should().BeEmpty( - "a queued response from the superseded timeline should not be processed after restore"); + ExternalRequest replayedRequest = Assert.Single(restoredEvents.OfType() + .Select(evt => evt.Request)); + + Assert.Empty(restoredEvents.OfType() ?? []); RunStatus statusAfterRestore = await run.GetStatusAsync(); - statusAfterRestore.Should().Be(RunStatus.PendingRequests, - "the restored run should remain pending until a post-restore response is sent"); + Assert.Equal(RunStatus.PendingRequests, statusAfterRestore); await run.SendResponseAsync(replayedRequest.CreateResponse("Again")); List completionEvents = await ReadToHaltAsync(run); - completionEvents.OfType().Should().BeEmpty( - "the restored request should complete cleanly once a new response is provided"); + Assert.Empty(completionEvents.OfType() ?? []); RunStatus finalStatus = await run.GetStatusAsync(); - finalStatus.Should().Be(RunStatus.Idle, - "the workflow should finish once the replayed request receives a fresh response"); + Assert.Equal(RunStatus.Idle, finalStatus); } /// @@ -347,8 +325,7 @@ internal async Task Checkpoint_Resume_PreservesFanInBarrierBufferedMessagesAsync ExternalRequest replayedRequest = await ResumeAndAssertBarrierReleasesAsync( env, checkpointManager, workflow, checkpoint, ["before", "after"]); - pendingRequest.RequestId.Should().Be(replayedRequest.RequestId, - "the replayed request should be the one from the checkpointed superstep"); + Assert.Equal(replayedRequest.RequestId, pendingRequest.RequestId); } /// @@ -400,8 +377,7 @@ internal async Task Checkpoint_Resume_PreservesFanInBarrierBufferedMessages_Mult ExternalRequest replayedRequest = await ResumeAndAssertBarrierReleasesAsync( env, checkpointManager, workflow, checkpoint, ["before-1", "before-2", "after"]); - pendingRequest.RequestId.Should().Be(replayedRequest.RequestId, - "the replayed request should match the one captured at checkpoint time"); + Assert.Equal(replayedRequest.RequestId, pendingRequest.RequestId); } /// @@ -483,8 +459,7 @@ Workflow BuildOuter() ExternalRequest replayedRequest = await ResumeAndAssertBarrierReleasesAsync( env, checkpointManager, BuildOuter(), checkpoint, ["before", "after"]); - pendingRequest.RequestId.Should().Be(replayedRequest.RequestId, - "the replayed subworkflow request should match the one captured at checkpoint time"); + Assert.Equal(replayedRequest.RequestId, pendingRequest.RequestId); } /// @@ -517,22 +492,17 @@ internal async Task Checkpoint_Resume_SubworkflowWithPendingRequests_Republishes List resumedEvents = await ReadToHaltAsync(resumed); ExternalRequest[] replayedRequests = [.. resumedEvents.OfType().Select(evt => evt.Request)]; - replayedRequests.Should().ContainSingle("the resumed parent workflow should surface the subworkflow request once"); - replayedRequests[0].RequestId.Should().Be(pendingRequest.RequestId, - "the replayed subworkflow request should match the checkpointed request"); - replayedRequests[0].PortInfo.PortId.Should().Be(pendingRequest.PortInfo.PortId, - "the replayed request should remain qualified through the subworkflow boundary"); + Assert.Single(replayedRequests); + Assert.Equal(pendingRequest.RequestId, replayedRequests[0].RequestId); + Assert.Equal(pendingRequest.PortInfo.PortId, replayedRequests[0].PortInfo.PortId); await resumed.SendResponseAsync(replayedRequests[0].CreateResponse("World")); List completionEvents = await ReadToHaltAsync(resumed); - completionEvents.OfType().Should().BeEmpty( - "the resumed subworkflow request should not be replayed twice"); - completionEvents.OfType().Should().BeEmpty( - "subworkflow replay should not introduce workflow errors"); + Assert.Empty(completionEvents.OfType() ?? []); + Assert.Empty(completionEvents.OfType() ?? []); RunStatus statusAfterSubworkflowResponse = await resumed.GetStatusAsync(); - statusAfterSubworkflowResponse.Should().Be(RunStatus.Idle, - "the resumed subworkflow should continue after responding to the replayed request"); + Assert.Equal(RunStatus.Idle, statusAfterSubworkflowResponse); } /// @@ -569,7 +539,7 @@ internal async Task Checkpoint_Resume_WithRepublishDisabled_DoesNotEmitRequestIn } } - checkpoint.Should().NotBeNull(); + Assert.NotNull(checkpoint); } // Act: Resume with republishPendingEvents: false via the internal API. @@ -587,8 +557,7 @@ internal async Task Checkpoint_Resume_WithRepublishDisabled_DoesNotEmitRequestIn } } - requestEventCount.Should().Be(0, - "no RequestInfoEvent should be emitted when republishPendingEvents is false"); + Assert.Equal(0, requestEventCount); } private static Workflow CreateSimpleRequestWorkflow( @@ -633,8 +602,8 @@ private static Workflow CreateCheckpointedSubworkflowRequestWorkflow() } } - pendingRequest.Should().NotBeNull("the workflow should have emitted a pending request"); - checkpoint.Should().NotBeNull("the workflow should have produced a checkpoint"); + Assert.NotNull(pendingRequest); + Assert.NotNull(checkpoint); return (pendingRequest!, checkpoint!); } @@ -679,26 +648,20 @@ private static async ValueTask ResumeAndAssertBarrierReleasesAs .ResumeStreamingAsync(workflow, checkpoint); List resumedEvents = await ReadToHaltAsync(resumed); - ExternalRequest replayedRequest = resumedEvents.OfType() - .Select(evt => evt.Request) - .Should() - .ContainSingle("resume should replay exactly one pending request") - .Subject; + ExternalRequest replayedRequest = Assert.Single(resumedEvents.OfType() + .Select(evt => evt.Request)); await resumed.SendResponseAsync(replayedRequest.CreateResponse(new ApprovalReply("yes"))); List completionEvents = await ReadToHaltAsync(resumed); - completionEvents.OfType().Should().BeEmpty( - "resuming across a partially satisfied fan-in barrier should not raise workflow errors"); + Assert.Empty(completionEvents.OfType() ?? []); string[] outputs = [.. completionEvents.OfType().Select(evt => evt.Source)]; - outputs.Should().BeEquivalentTo(expectedBarrierSources, - "the barrier should release every expected contribution"); + Assert.Equivalent(expectedBarrierSources, outputs); RunStatus status = await resumed.GetStatusAsync(); - status.Should().Be(RunStatus.Idle, - "the resumed run should halt cleanly once every barrier source has contributed"); + Assert.Equal(RunStatus.Idle, status); return replayedRequest; } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/CheckpointVersionToleranceTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/CheckpointVersionToleranceTests.cs index e4c80bf7ff9..260d52890a0 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/CheckpointVersionToleranceTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/CheckpointVersionToleranceTests.cs @@ -7,7 +7,6 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.Agents.AI.Workflows.Checkpointing; using Microsoft.Agents.AI.Workflows.InProc; @@ -57,8 +56,8 @@ internal async Task Test_Checkpoint_Resumes_AfterAssemblyVersionRewriteAsync(Exe } } - checkpoint.Should().NotBeNull(); - store.MutationApplied.Should().BeFalse(); + Assert.NotNull(checkpoint); + Assert.False(store.MutationApplied); // Resume against the mutated store, which rewrites every Version=X.Y.Z.W in the persisted JSON. Func resume = async () => @@ -71,8 +70,8 @@ internal async Task Test_Checkpoint_Resumes_AfterAssemblyVersionRewriteAsync(Exe } }; - await resume.Should().NotThrowAsync("resume must succeed when persisted assembly versions differ from loaded ones"); - store.MutationApplied.Should().BeTrue(); + Assert.Null(await Record.ExceptionAsync(resume)); + Assert.True(store.MutationApplied); } /// diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/ConcurrentWorkflowBuilderTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/ConcurrentWorkflowBuilderTests.cs index 33405a61e9e..eae4cc64ff8 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/ConcurrentWorkflowBuilderTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/ConcurrentWorkflowBuilderTests.cs @@ -6,7 +6,6 @@ using System.Runtime.CompilerServices; using System.Text.RegularExpressions; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.Agents.AI.Workflows.UnitTests.Futures; using Microsoft.Extensions.AI; @@ -67,10 +66,8 @@ public void Test_ConcurrentWorkflowBuilder_DefaultDesignationsMatchSpec() .Build(); Dictionary> designations = workflow.OutputExecutors; - designations.Where(kvp => kvp.Value.Count == 0) - .Should().ContainSingle("ConcurrentEndExecutor is the sole terminal output by default"); - designations.Where(kvp => kvp.Value.Contains(OutputTag.Intermediate)) - .Should().HaveCount(6, "every agent (3) and per-agent accumulator (3) is designated intermediate by default"); + Assert.Single(designations, kvp => kvp.Value.Count == 0); + Assert.Equal(6, (designations.Where(kvp => kvp.Value.Contains(OutputTag.Intermediate)))?.Count()); } [Fact] @@ -87,12 +84,9 @@ public void Test_ConcurrentWorkflowBuilder_ExplicitDesignationsReplaceDefaults() Dictionary> designations = workflow.OutputExecutors; - designations.Should().HaveCount(2, - "only the two explicitly-designated agents land on the inner builder; the end + accumulator defaults are suppressed"); - designations.Values.Where(tags => tags.Count == 0) - .Should().ContainSingle("agent1 is the only terminal designation"); - designations.Values.Where(tags => tags.Contains(OutputTag.Intermediate)) - .Should().ContainSingle("agent2 is the only intermediate designation"); + Assert.Equal(2, designations.Count); + Assert.Single(designations.Values, tags => tags.Count == 0); + Assert.Single(designations.Values, tags => tags.Contains(OutputTag.Intermediate)); } [Fact] @@ -105,7 +99,7 @@ public void Test_ConcurrentWorkflowBuilder_DesignationForNonParticipantThrows() .WithIntermediateOutputFrom([stranger]); Action build = () => builder.Build(); - build.Should().Throw().WithMessage("*stranger*"); + Assert.Contains("stranger", (Assert.Throws(build)).Message); } [Fact] @@ -115,7 +109,7 @@ public void Test_ConcurrentWorkflowBuilder_WithNamePropagatesToWorkflow() .WithName("named-concurrent") .Build(); - workflow.Name.Should().Be("named-concurrent"); + Assert.Equal("named-concurrent", workflow.Name); } [Fact] @@ -125,7 +119,7 @@ public void Test_ConcurrentWorkflowBuilder_WithDescriptionPropagatesToWorkflow() .WithDescription("describes the concurrent fan-out/fan-in") .Build(); - workflow.Description.Should().Be("describes the concurrent fan-out/fan-in"); + Assert.Equal("describes the concurrent fan-out/fan-in", workflow.Description); } [Collection(FuturesSerialCollection.Name)] @@ -156,9 +150,8 @@ public async Task Test_ConcurrentWorkflowBuilder_AsAgent_OnlyTerminalDesignation .Select(n => n!) .ToHashSet(); - authoredBy.Should().Contain("agent1", "the designated agent must surface"); - authoredBy.Should().NotContain("agent2", - "the undesignated agent must not surface when only one is designated under Futures-on"); + Assert.Contains("agent1", authoredBy); + Assert.DoesNotContain("agent2", authoredBy); } } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/DynamicRequestPortTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/DynamicRequestPortTests.cs index ce307340871..41904f85193 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/DynamicRequestPortTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/DynamicRequestPortTests.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Reflection; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.Agents.AI.Workflows.Checkpointing; using Microsoft.Agents.AI.Workflows.Execution; @@ -41,9 +40,8 @@ public static async ValueTask CreateAsync(string request if (validate) { - result.ExternalRequestContext - .ExternalRequests.Should().HaveCount(1) - .And.AllSatisfy(request => request.PortInfo.Should().Be(result.PortBinding.Port.ToPortInfo())); + Assert.Single(result.ExternalRequestContext.ExternalRequests); + Assert.All(result.ExternalRequestContext.ExternalRequests, request => Assert.Equal(result.PortBinding.Port.ToPortInfo(), request.PortInfo)); } return result; @@ -78,10 +76,10 @@ public async Task Test_DynamicRequestPort_DeliversExpectedResponseAsync() await context.InvokeExecutorWithResponseAsync(request.CreateResponse(13)); string portId = request.PortInfo.PortId; - context.Executor.ReceivedResponses.Should().HaveCount(1) - .And.ContainKey(portId); - context.Executor.ReceivedResponses[portId].Should().HaveCount(1); - context.Executor.ReceivedResponses[portId].First().Should().Be(13); + Assert.Single(context.Executor.ReceivedResponses); + Assert.Contains(portId, context.Executor.ReceivedResponses!); + Assert.Single(context.Executor.ReceivedResponses[portId]); + Assert.Equal(13, context.Executor.ReceivedResponses[portId].First()); } [Fact] @@ -93,7 +91,7 @@ public async Task Test_DynamicRequestPort_ThrowsOnWrongPortAsync() ExternalRequest fakeRequest = new(RequestPort.Create("port2").ToPortInfo(), request.RequestId, request.Data); Func act = async () => await context.InvokeExecutorWithResponseAsync(fakeRequest.CreateResponse(13)); - (await act.Should().ThrowAsync()) - .WithInnerException(); + TargetInvocationException exception = await Assert.ThrowsAsync(act); + Assert.IsType(exception.InnerException); } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/EdgeMapSmokeTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/EdgeMapSmokeTests.cs index 34d477600ef..1986b7beec3 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/EdgeMapSmokeTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/EdgeMapSmokeTests.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.Agents.AI.Workflows.Checkpointing; using Microsoft.Agents.AI.Workflows.Execution; using Microsoft.Agents.AI.Workflows.Specialized; @@ -27,11 +26,11 @@ public async Task Test_EdgeMap_RoutesStaticPortAsync() ExternalResponse responseMessage = new(staticPort.ToPortInfo(), "Request1", new(12)); DeliveryMapping? mapping = await edgeMap.PrepareDeliveryForResponseAsync(responseMessage); - mapping.Should().NotBeNull(); + Assert.NotNull(mapping); List deliveries = mapping.Deliveries.ToList(); - deliveries.Should().HaveCount(1).And.AllSatisfy(delivery => delivery.TargetId.Should().Be(executor.Id)); - deliveries[0].Envelope.Message.Should().Be(responseMessage); + Assert.All(deliveries, delivery => Assert.Equal(executor.Id, delivery.TargetId)); + Assert.Equal(responseMessage, deliveries[0].Envelope.Message); } [Fact] @@ -53,11 +52,11 @@ async ValueTask RunPortTestAsync(string portId) ExternalResponse responseMessage = new(binding.Port.ToPortInfo(), $"RequestFor[{portId}]", new(10)); DeliveryMapping? mapping = await edgeMap.PrepareDeliveryForResponseAsync(responseMessage); - mapping.Should().NotBeNull(); + Assert.NotNull(mapping); List deliveries = mapping.Deliveries.ToList(); - deliveries.Should().HaveCount(1).And.AllSatisfy(delivery => delivery.TargetId.Should().Be(executor.Id)); - deliveries[0].Envelope.Message.Should().Be(responseMessage); + Assert.All(deliveries, delivery => Assert.Equal(executor.Id, delivery.TargetId)); + Assert.Equal(responseMessage, deliveries[0].Envelope.Message); } } @@ -82,7 +81,7 @@ async ValueTask RunPortTestAsync(string portId) ExternalResponse responseMessage = new(fakePort.ToPortInfo(), $"RequestFor[{portId}]", new(10)); Func> mappingTask = async () => await edgeMap.PrepareDeliveryForResponseAsync(responseMessage); - await mappingTask.Should().ThrowAsync(); + await Assert.ThrowsAsync(mappingTask); } } @@ -107,18 +106,20 @@ public async Task Test_EdgeMap_MaintainsFanInEdgeStateAsync() ], edgeMap); DeliveryMapping? mapping = await edgeMap.PrepareDeliveryForEdgeAsync(fanInEdge, new("part1", "executor1")); - mapping.Should().BeNull(); + Assert.Null(mapping); mapping = await edgeMap.PrepareDeliveryForEdgeAsync(fanInEdge, new("part2", "executor2")); - mapping.Should().NotBeNull(); + Assert.NotNull(mapping); List deliveries = mapping.Deliveries.ToList(); - deliveries.Should().HaveCount(2).And.AllSatisfy(delivery => delivery.TargetId.Should().Be("executor3")); + Assert.Equal(2, deliveries.Count); + Assert.All(deliveries!, delivery => Assert.Equal("executor3", delivery.TargetId)); HashSet expectedMessages = ["part1", "part2"]; foreach (MessageDelivery delivery in deliveries) { - string message = delivery.Envelope.As()!; + string? message = Assert.IsType(delivery.Envelope.Message).As(); + Assert.NotNull(message); expectedMessages.Remove(message); } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/EdgeRunnerTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/EdgeRunnerTests.cs index cc5d5a3c620..22ae41c7010 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/EdgeRunnerTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/EdgeRunnerTests.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.Agents.AI.Workflows.Execution; namespace Microsoft.Agents.AI.Workflows.UnitTests; @@ -48,12 +47,12 @@ private static async Task CreateAndRunDirectedEdgeTestAsync(bool? conditionMatch if (expectMessage) { - mapping.Should().NotBeNull(); + Assert.NotNull(mapping); mapping.CheckDeliveries(["executor2"], [MessageVariant1]); } else { - mapping.Should().BeNull(); + Assert.Null(mapping); } } @@ -122,11 +121,11 @@ private static async Task CreateAndRunFanOutEdgeTestAsync(bool? assignerSelectsE if (!expectForwardFrom2 && !expectForwardFrom3) { - mapping.Should().BeNull(); + Assert.Null(mapping); } else { - mapping.Should().NotBeNull(); + Assert.NotNull(mapping); mapping.CheckDeliveries(expectedReceivers, ["test"]); } } @@ -181,22 +180,22 @@ async ValueTask RunIterationAsync() //await runner.ChaseAsync("executor1", new("part1"), state, tracer: null); //MessageDeliveryValidation.CheckForwarded(runContext.QueuedMessages); DeliveryMapping? mapping = await runner.ChaseEdgeAsync(new("part1", "executor1"), stepTracer: null, CancellationToken.None); - mapping.Should().BeNull(); + Assert.Null(mapping); //await runner.ChaseAsync("executor2", new("part-for-1", targetId: "executor1"), state, tracer: null); //MessageDeliveryValidation.CheckForwarded(runContext.QueuedMessages); mapping = await runner.ChaseEdgeAsync(new("part-for-1", "executor2", targetId: "executor1"), stepTracer: null, CancellationToken.None); - mapping.Should().BeNull(); + Assert.Null(mapping); //await runner.ChaseAsync("executor1", new("part2", targetId: "executor3"), state, tracer: null); //MessageDeliveryValidation.CheckForwarded(runContext.QueuedMessages); mapping = await runner.ChaseEdgeAsync(new("part2", "executor1", targetId: "executor3"), stepTracer: null, CancellationToken.None); - mapping.Should().BeNull(); + Assert.Null(mapping); //await runner.ChaseAsync("executor2", new("final part"), state, tracer: null); //MessageDeliveryValidation.CheckForwarded(runContext.QueuedMessages, ("executor3", ["part1", "part2", "final part"])); mapping = await runner.ChaseEdgeAsync(new("final part", "executor2"), stepTracer: null, CancellationToken.None); - mapping.Should().NotBeNull(); + Assert.NotNull(mapping); mapping.CheckDeliveries(["executor3"], ["part1", "part2", "final part"]); } } @@ -232,7 +231,7 @@ public async Task Test_FanInEdgeRunner_ConcurrentProcessingAsync() // Assert: exactly one task should return a non-null mapping with all messages DeliveryMapping?[] nonNullResults = results.Where(r => r is not null).ToArray(); - nonNullResults.Should().HaveCount(1, $"iteration {iteration}: exactly one thread should release the batch"); + Assert.Single(nonNullResults); DeliveryMapping mapping = nonNullResults[0]!; HashSet expectedMessages = [.. sourceIds.Select(id => (object)$"msg-from-{id}")]; diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/ExternalResponsePortCorrelationTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/ExternalResponsePortCorrelationTests.cs index 6d16b5a8a65..689e3fce1bd 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/ExternalResponsePortCorrelationTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/ExternalResponsePortCorrelationTests.cs @@ -2,7 +2,6 @@ using System.Threading; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.Agents.AI.Workflows.Checkpointing; using Microsoft.Agents.AI.Workflows.Execution; using Microsoft.Agents.AI.Workflows.InProc; @@ -49,13 +48,15 @@ public async Task AddExternalResponseAsync_RejectsForgedPortIdAsync() // Assert: validation fires when the queued delivery is drained. var act = async () => await runner.RunContext.AdvanceAsync(CancellationToken.None); - var exception = await act.Should().ThrowAsync(); + var exception = await Assert.ThrowsAsync(act); - string message = exception.Which.Message; - message.Should().Contain($"'{PortBId}'").And.Contain(pending.RequestId).And.NotContain($"'{PortAId}'"); + string message = exception.Message; + Assert.Contains($"'{PortBId}'", message); + Assert.Contains(pending.RequestId, message); + Assert.DoesNotContain($"'{PortAId}'", message); // Pending request survives the rejection so the legitimate responder can still complete it. - ((ISuperStepRunner)runner).HasUnservicedRequests.Should().BeTrue(); + Assert.True(((ISuperStepRunner)runner).HasUnservicedRequests); } [Fact] @@ -71,7 +72,7 @@ public async Task AddExternalResponseAsync_AllowsLegitimateResponseAfterRejected await runner.RunContext.AddExternalResponseAsync(forged); var rejectAct = async () => await runner.RunContext.AdvanceAsync(CancellationToken.None); - await rejectAct.Should().ThrowAsync(); + await Assert.ThrowsAsync(rejectAct); // Legitimate responder retries with the correct PortInfo. ExternalResponse legitimate = pending.CreateResponse(42); @@ -79,8 +80,8 @@ public async Task AddExternalResponseAsync_AllowsLegitimateResponseAfterRejected var legitimateAct = async () => await runner.RunContext.AdvanceAsync(CancellationToken.None); - await legitimateAct.Should().NotThrowAsync(); - ((ISuperStepRunner)runner).HasUnservicedRequests.Should().BeFalse(); + Assert.Null(await Record.ExceptionAsync(legitimateAct)); + Assert.False(((ISuperStepRunner)runner).HasUnservicedRequests); } [Fact] @@ -98,9 +99,9 @@ public async Task AddExternalResponseAsync_AllowsMatchingPortIdAsync() await runner.RunContext.AddExternalResponseAsync(legitimate); var act = async () => await runner.RunContext.AdvanceAsync(CancellationToken.None); - await act.Should().NotThrowAsync(); + Assert.Null(await Record.ExceptionAsync(act)); - ((ISuperStepRunner)runner).HasUnservicedRequests.Should().BeFalse(); + Assert.False(((ISuperStepRunner)runner).HasUnservicedRequests); } [Fact] @@ -115,7 +116,7 @@ public async Task AddExternalResponseAsync_RejectsUnknownRequestIdAsync() await runner.RunContext.AddExternalResponseAsync(stray); var act = async () => await runner.RunContext.AdvanceAsync(CancellationToken.None); - var exception = await act.Should().ThrowAsync(); - exception.Which.Message.Should().Contain("No pending request with ID no-such-request"); + var exception = await Assert.ThrowsAsync(act); + Assert.Contains("No pending request with ID no-such-request", exception.Message); } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/FileSystemJsonCheckpointStoreTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/FileSystemJsonCheckpointStoreTests.cs index 405357e6200..57d262348f9 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/FileSystemJsonCheckpointStoreTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/FileSystemJsonCheckpointStoreTests.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Text.Json; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.Agents.AI.Workflows.Checkpointing; namespace Microsoft.Agents.AI.Workflows.UnitTests; @@ -103,14 +102,14 @@ public async Task CreateCheckpointAsync_ShouldPersistIndexToDiskBeforeDisposeAsy long fileSizeBeforeDispose = indexFile.Length; // Data should already be on disk (file size > 0) before we dispose - fileSizeBeforeDispose.Should().BeGreaterThan(0, "index.jsonl should be flushed to disk after CreateCheckpointAsync"); + Assert.True((fileSizeBeforeDispose) > (0)); // Dispose to release file lock before final verification store.Dispose(); string[] lines = File.ReadAllLines(indexPath); - lines.Should().HaveCount(1); - lines[0].Should().Contain(checkpoint.CheckpointId); + Assert.Single(lines); + Assert.Contains(checkpoint.CheckpointId, lines[0]); } private async ValueTask Run_EscapeRootFolderTestAsync(string escapingPath) @@ -123,21 +122,21 @@ private async ValueTask Run_EscapeRootFolderTestAsync(string escapingPath) // Check that the naive path is actually outside the temp directory to validate the test is meaningful FileInfo naiveCheckpointFile = new(naivePath); - tempDirectory.IsParentOf(naiveCheckpointFile).Should().BeFalse("The naive path should be outside the root folder to validate that escaping is necessary."); + Assert.False(tempDirectory.IsParentOf(naiveCheckpointFile)); // Act CheckpointInfo checkpointInfo = await store.CreateCheckpointAsync(escapingPath, TestData); // Assert string naivePathWithCheckpointId = Path.Combine(tempDirectory.DirectoryInfo.FullName, $"{escapingPath}_{checkpointInfo.CheckpointId}.json"); - new FileInfo(naivePathWithCheckpointId).Exists.Should().BeFalse("The naive path should not be used to save a checkpoint file."); + Assert.False(new FileInfo(naivePathWithCheckpointId).Exists); string actualFileName = store.GetFileNameForCheckpoint(escapingPath, checkpointInfo); string actualFilePath = Path.Combine(tempDirectory.DirectoryInfo.FullName, actualFileName); FileInfo actualFile = new(actualFilePath); - tempDirectory.IsParentOf(actualFile).Should().BeTrue("The actual checkpoint should be saved inside the root folder."); - actualFile.Exists.Should().BeTrue("The actual path should be used to save a checkpoint file."); + Assert.True(tempDirectory.IsParentOf(actualFile)); + Assert.True(actualFile.Exists); } [Fact] @@ -187,7 +186,7 @@ public async Task RetrieveIndexAsync_ShouldNotReturnDuplicates_WhenIndexContains CheckpointInfo[] index = (await reopenedStore.RetrieveIndexAsync(sessionId)).ToArray(); // Assert: the load path dedupes, so the checkpoint appears exactly once. - index.Should().ContainSingle().Which.Should().Be(checkpoint); + Assert.Equal(checkpoint, Assert.Single(index)); } [Fact] @@ -206,8 +205,8 @@ public async Task RetrieveIndexAsync_ShouldReturnDistinctCheckpointsInCommitOrde CheckpointInfo[] index = (await store.RetrieveIndexAsync(sessionId)).ToArray(); // Assert: no duplicates, and commit order preserved. - index.Should().OnlyHaveUniqueItems(); - index.Should().Equal(first, second, third); + Assert.Equal(index.Length, index.Distinct().Count()); + Assert.Equal([first, second, third], index); } private const string InvalidPathCharsWin32 = "\\/:*?\"<>|"; @@ -227,7 +226,7 @@ public async Task CreateCheckpointAsync_EscapesInvalidCharsAsync(string invalidC string runId = $"prefix_{invalidChars}_suffix"; Func createCheckpointAction = async () => await store.CreateCheckpointAsync(runId, TestData); - await createCheckpointAction.Should().NotThrowAsync(); + Assert.Null(await Record.ExceptionAsync(createCheckpointAction)); } [Fact] @@ -245,8 +244,8 @@ public async Task RetrieveCheckpointAsync_ShouldReturnPersistedDataAsync() JsonElement retrieved = await store.RetrieveCheckpointAsync(sessionId, checkpoint); // Assert - retrieved.GetProperty("name").GetString().Should().Be("test"); - retrieved.GetProperty("value").GetInt32().Should().Be(42); + Assert.Equal("test", retrieved.GetProperty("name").GetString()); + Assert.Equal(42, retrieved.GetProperty("value").GetInt32()); } [Fact] @@ -268,16 +267,16 @@ public async Task RetrieveIndexAsync_ShouldOnlyReturnCheckpointsForRequestedSess CheckpointInfo[] firstSessionIndex = (await store.RetrieveIndexAsync(firstSessionId)).ToArray(); // Assert - firstSessionIndex.Should().ContainSingle().Which.Should().Be(firstCheckpoint); - firstSessionIndex.Should().NotContain(secondCheckpoint); + Assert.Equal(firstCheckpoint, Assert.Single(firstSessionIndex)); + Assert.DoesNotContain(secondCheckpoint, firstSessionIndex); } using (FileSystemJsonCheckpointStore reopenedStore = new(tempDirectory)) { CheckpointInfo[] secondSessionIndex = (await reopenedStore.RetrieveIndexAsync(secondSessionId)).ToArray(); - secondSessionIndex.Should().ContainSingle().Which.Should().Be(secondCheckpoint); - secondSessionIndex.Should().NotContain(firstCheckpoint); + Assert.Equal(secondCheckpoint, Assert.Single(secondSessionIndex)); + Assert.DoesNotContain(firstCheckpoint, secondSessionIndex); } } @@ -301,18 +300,18 @@ public async Task RetrieveIndexAsync_ShouldFilterByParentCheckpointAsync() CheckpointInfo[] childIndex = (await store.RetrieveIndexAsync(sessionId, parentCheckpoint)).ToArray(); // Assert - childIndex.Should().ContainSingle().Which.Should().Be(childCheckpoint); - childIndex.Should().NotContain(parentCheckpoint); - childIndex.Should().NotContain(unrelatedCheckpoint); + Assert.Equal(childCheckpoint, Assert.Single(childIndex)); + Assert.DoesNotContain(parentCheckpoint, childIndex); + Assert.DoesNotContain(unrelatedCheckpoint, childIndex); } using (FileSystemJsonCheckpointStore reopenedStore = new(tempDirectory)) { CheckpointInfo[] childIndex = (await reopenedStore.RetrieveIndexAsync(sessionId, parentCheckpoint)).ToArray(); - childIndex.Should().ContainSingle().Which.Should().Be(childCheckpoint); - childIndex.Should().NotContain(parentCheckpoint); - childIndex.Should().NotContain(unrelatedCheckpoint); + Assert.Equal(childCheckpoint, Assert.Single(childIndex)); + Assert.DoesNotContain(parentCheckpoint, childIndex); + Assert.DoesNotContain(unrelatedCheckpoint, childIndex); } } @@ -342,7 +341,7 @@ public async Task RetrieveIndexAsync_ShouldKeepLegacyEntriesDiscoverableWithPare CheckpointInfo[] childIndex = (await reopenedStore.RetrieveIndexAsync(sessionId, parentCheckpoint)).ToArray(); // Assert - childIndex.Should().ContainSingle().Which.Should().Be(childCheckpoint); + Assert.Equal(childCheckpoint, Assert.Single(childIndex)); } [Fact] @@ -373,6 +372,6 @@ public async Task RetrieveIndexAsync_ShouldKeepLegacyChildDiscoverableWithUnrela CheckpointInfo[] childIndex = (await reopenedStore.RetrieveIndexAsync(sessionId, unrelatedCheckpoint)).ToArray(); // Assert - childIndex.Should().ContainSingle().Which.Should().Be(childCheckpoint); + Assert.Equal(childCheckpoint, Assert.Single(childIndex)); } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/FunctionExecutorTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/FunctionExecutorTests.cs index 164ac58ed86..cbb247cb07a 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/FunctionExecutorTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/FunctionExecutorTests.cs @@ -5,7 +5,6 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -using FluentAssertions; namespace Microsoft.Agents.AI.Workflows.UnitTests; @@ -53,24 +52,33 @@ internal async ValueTask Run_FunctionExecutor_MessageHandler internal static void CheckInvoked(ExecutorTestResult result, TMessage expectedInput, object? expectedCallResult = null) where TMessage : class { - result.CallResult.Should().Be(expectedCallResult); + Assert.Equal(expectedCallResult, result.CallResult); - result.Context.EmittedEvents.Should().Contain(evt => evt is ExecutorInvokedEvent - && ((ExecutorInvokedEvent)evt).Data as TMessage == expectedInput) - .And.Contain(evt => evt is ExecutorCompletedEvent - && ((ExecutorCompletedEvent)evt).Data == expectedCallResult); + Assert.Contains(result.Context.EmittedEvents, evt => evt is ExecutorInvokedEvent invoked + && MatchesExpected(invoked.Data, expectedInput)); + Assert.Contains(result.Context.EmittedEvents, evt => evt is ExecutorCompletedEvent completed + && MatchesExpected(completed.Data, expectedCallResult)); } internal static void CheckInvoked(ExecutorTestResult result, TMessage expectedInput, TOutput expectedCallResult) where TMessage : class where TOutput : class { - result.CallResult.Should().Be(expectedCallResult); + Assert.Equal(expectedCallResult, result.CallResult); - result.Context.EmittedEvents.Should().Contain(evt => evt is ExecutorInvokedEvent - && ((ExecutorInvokedEvent)evt).Data as TMessage == expectedInput) - .And.Contain(evt => evt is ExecutorCompletedEvent - && ((ExecutorCompletedEvent)evt).Data as TOutput == expectedCallResult); + Assert.Contains(result.Context.EmittedEvents, evt => evt is ExecutorInvokedEvent invoked + && MatchesExpected(invoked.Data, expectedInput)); + Assert.Contains(result.Context.EmittedEvents, evt => evt is ExecutorCompletedEvent completed + && MatchesExpected(completed.Data, expectedCallResult)); + } + + private static bool MatchesExpected(object? actual, object? expected) + { + object? normalizedActual = actual is PortableValue portableValue && expected is not null + ? portableValue.AsType(expected.GetType()) + : actual; + + return Equals(expected, normalizedActual); } internal TestWorkflowContext CreateWorkflowContext(Executor executor) => new(executor.Id); @@ -147,8 +155,8 @@ public void Test_FunctionExecutor__1_SendTypesAreRegistered(bool useAsync, bool ProtocolDescriptor protocol = executor.DescribeProtocol(); // Assert - protocol.Sends.Should().BeEquivalentTo([typeof(TextMessage)]); - protocol.Yields.Should().BeEmpty(); + Assert.Equivalent(new[] { typeof(TextMessage) }, protocol.Sends); + Assert.Empty(protocol.Yields ?? []); // Helpers [SendsMessage(typeof(TextMessage))] @@ -197,8 +205,8 @@ FunctionExecutor executor ProtocolDescriptor protocol = executor.DescribeProtocol(); // Assert - protocol.Sends.Should().BeEquivalentTo([typeof(TextMessage)]); - protocol.Yields.Should().BeEmpty(); + Assert.Equivalent(new[] { typeof(TextMessage) }, protocol.Sends); + Assert.Empty(protocol.Yields ?? []); // Helpers [SendsMessage(typeof(TextMessage))] @@ -246,8 +254,8 @@ public void Test_FunctionExecutor__1_YieldTypesAreRegistered(bool useAsync, bool ProtocolDescriptor protocol = executor.DescribeProtocol(); // Assert - protocol.Yields.Should().BeEquivalentTo([typeof(DataMessage)]); - protocol.Sends.Should().BeEmpty(); + Assert.Equivalent(new[] { typeof(DataMessage) }, protocol.Yields); + Assert.Empty(protocol.Sends ?? []); // Helpers [YieldsOutput(typeof(DataMessage))] @@ -296,8 +304,8 @@ FunctionExecutor executor ProtocolDescriptor protocol = executor.DescribeProtocol(); // Assert - protocol.Yields.Should().BeEquivalentTo([typeof(DataMessage)]); - protocol.Sends.Should().BeEmpty(); + Assert.Equivalent(new[] { typeof(DataMessage) }, protocol.Yields); + Assert.Empty(protocol.Sends ?? []); // Helpers [YieldsOutput(typeof(DataMessage))] @@ -347,8 +355,8 @@ public void Test_FunctionExecutor__1_ExecutorOptionsAreNoOp(bool useAsync, bool : new(nameof(FunctionExecutor<>), MessageHandler, options); ProtocolDescriptor protocol = executor.DescribeProtocol(); - protocol.Sends.Should().BeEmpty(); - protocol.Yields.Should().BeEmpty(); + Assert.Empty(protocol.Sends ?? []); + Assert.Empty(protocol.Yields ?? []); // Helpers ValueTask MessageHandlerAsync(TextMessage message, IWorkflowContext context, CancellationToken cancellationToken) @@ -392,26 +400,29 @@ public async Task Test_FunctionExecutor__2_ExecutorOptionsCauseCorrectRegistrati CheckInvoked(result, TestMessage, TestDataMessage); if (autoSendReturnValue) { - protocol.Sends.Should().BeEquivalentTo([typeof(DataMessage)]); - result.Context.SentMessages.Should().ContainEquivalentOf(TestDataMessage); + Assert.Equivalent(new[] { typeof(DataMessage) }, protocol.Sends); + Assert.Contains(result.Context.SentMessages, IsTestDataMessage); } else { - protocol.Sends.Should().BeEmpty(); - result.Context.SentMessages.Should().NotContainEquivalentOf(TestDataMessage); + Assert.Empty(protocol.Sends ?? []); + Assert.DoesNotContain(result.Context.SentMessages, IsTestDataMessage); } if (autoYieldReturnValue) { - protocol.Yields.Should().BeEquivalentTo([typeof(DataMessage)]); - result.Context.YieldedOutputs.Should().ContainEquivalentOf(TestDataMessage); + Assert.Equivalent(new[] { typeof(DataMessage) }, protocol.Yields); + Assert.Contains(result.Context.YieldedOutputs, IsTestDataMessage); } else { - protocol.Yields.Should().BeEmpty(); - result.Context.YieldedOutputs.Should().NotContainEquivalentOf(TestDataMessage); + Assert.Empty(protocol.Yields ?? []); + Assert.DoesNotContain(result.Context.YieldedOutputs, IsTestDataMessage); } + static bool IsTestDataMessage(object message) + => TestDataMessage.Equals(message is PortableValue portableValue ? portableValue.As() : message); + // Helpers ValueTask MessageHandlerAsync(TextMessage message, IWorkflowContext context, CancellationToken cancellationToken) => new(new DataMessage(message)); diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Futures/Futures.AgentResponseOutputFilteringAndTaggingTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Futures/Futures.AgentResponseOutputFilteringAndTaggingTests.cs index 955a354332b..ed40283589a 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Futures/Futures.AgentResponseOutputFilteringAndTaggingTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Futures/Futures.AgentResponseOutputFilteringAndTaggingTests.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.Extensions.AI; namespace Microsoft.Agents.AI.Workflows.UnitTests.Futures; @@ -72,10 +71,10 @@ public async Task Test_Runner_LegacyAgentResponseBypass_RaisesUntaggedEventAsync List events = await RunAsync(workflow, "go"); - AgentResponseEvent emitted = events.OfType().Should().ContainSingle().Subject; - emitted.ExecutorId.Should().Be(SourceId); - emitted.Tags.Should().BeEmpty("legacy bypass attaches no tags"); - emitted.IsIntermediate().Should().BeFalse(); + AgentResponseEvent emitted = Assert.Single(events.OfType()); + Assert.Equal(SourceId, emitted.ExecutorId); + Assert.Empty(emitted.Tags ?? []); + Assert.False(emitted.IsIntermediate()); } // F2 @@ -87,8 +86,8 @@ public async Task Test_Runner_LegacyAgentResponseUpdateBypass_RaisesUntaggedEven List events = await RunAsync(workflow, "go"); - AgentResponseUpdateEvent emitted = events.OfType().Should().ContainSingle().Subject; - emitted.Tags.Should().BeEmpty(); + AgentResponseUpdateEvent emitted = Assert.Single(events.OfType()); + Assert.Empty(emitted.Tags ?? []); } // F3 @@ -100,9 +99,9 @@ public async Task Test_Runner_LegacyBypassIgnoresDesignationAsync() List events = await RunAsync(workflow, "go"); - AgentResponseEvent emitted = events.OfType().Should().ContainSingle().Subject; - emitted.Tags.Should().BeEmpty("legacy bypass ignores the designation entirely"); - emitted.IsIntermediate().Should().BeFalse("legacy bypass does not propagate tags"); + AgentResponseEvent emitted = Assert.Single(events.OfType()); + Assert.Empty(emitted.Tags ?? []); + Assert.False(emitted.IsIntermediate()); } // F4 @@ -114,7 +113,7 @@ public async Task Test_Runner_LegacyPocoIsFilteredAsync() List events = await RunAsync(workflow, "go"); - events.OfType().Should().BeEmpty("POCO outputs always go through the filter; undesignated source is dropped"); + Assert.Empty(events.OfType() ?? []); } // F5 @@ -126,8 +125,7 @@ public async Task Test_Runner_UndesignatedAgentResponseIsFilteredWhenFuturesOnAs List events = await RunAsync(workflow, "go"); - events.OfType().Should().BeEmpty( - "with the future on, AgentResponse must be designated to surface"); + Assert.Empty(events.OfType() ?? []); } // F6 @@ -139,9 +137,9 @@ public async Task Test_Runner_DesignatedTerminalAgentResponseHasEmptyTagsAsync() List events = await RunAsync(workflow, "go"); - AgentResponseEvent emitted = events.OfType().Should().ContainSingle().Subject; - emitted.Tags.Should().BeEmpty("terminal designation carries no tag"); - emitted.IsIntermediate().Should().BeFalse(); + AgentResponseEvent emitted = Assert.Single(events.OfType()); + Assert.Empty(emitted.Tags ?? []); + Assert.False(emitted.IsIntermediate()); } // F7 @@ -153,9 +151,9 @@ public async Task Test_Runner_DesignatedIntermediateAgentResponseHasIntermediate List events = await RunAsync(workflow, "go"); - AgentResponseEvent emitted = events.OfType().Should().ContainSingle().Subject; - emitted.Tags.Should().BeEquivalentTo(new[] { OutputTag.Intermediate }); - emitted.IsIntermediate().Should().BeTrue(); + AgentResponseEvent emitted = Assert.Single(events.OfType()); + Assert.Equivalent(new[] { OutputTag.Intermediate }, emitted.Tags); + Assert.True(emitted.IsIntermediate()); } // F8 @@ -167,9 +165,9 @@ public async Task Test_Runner_DesignatedIntermediateAgentResponseUpdateHasInterm List events = await RunAsync(workflow, "go"); - AgentResponseUpdateEvent emitted = events.OfType().Should().ContainSingle().Subject; - emitted.Tags.Should().BeEquivalentTo(new[] { OutputTag.Intermediate }); - emitted.IsIntermediate().Should().BeTrue(); + AgentResponseUpdateEvent emitted = Assert.Single(events.OfType()); + Assert.Equivalent(new[] { OutputTag.Intermediate }, emitted.Tags); + Assert.True(emitted.IsIntermediate()); } // F9 @@ -185,10 +183,9 @@ public async Task Test_Runner_TagsAccumulateOutputThenIntermediateAsync() List events = await RunAsync(workflow, "go"); - AgentResponseEvent emitted = events.OfType().Should().ContainSingle().Subject; - emitted.Tags.Should().BeEquivalentTo(new[] { OutputTag.Intermediate }, - "terminal+intermediate union is {{ Intermediate }} (terminal contributes the entry but no tag)"); - emitted.IsIntermediate().Should().BeTrue(); + AgentResponseEvent emitted = Assert.Single(events.OfType()); + Assert.Equivalent(new[] { OutputTag.Intermediate }, emitted.Tags); + Assert.True(emitted.IsIntermediate()); } // F10 @@ -204,9 +201,9 @@ public async Task Test_Runner_TagsAccumulateIntermediateThenOutputAsync() List events = await RunAsync(workflow, "go"); - AgentResponseEvent emitted = events.OfType().Should().ContainSingle().Subject; - emitted.Tags.Should().BeEquivalentTo(new[] { OutputTag.Intermediate }, "designation order is irrelevant"); - emitted.IsIntermediate().Should().BeTrue(); + AgentResponseEvent emitted = Assert.Single(events.OfType()); + Assert.Equivalent(new[] { OutputTag.Intermediate }, emitted.Tags); + Assert.True(emitted.IsIntermediate()); } // F11 @@ -218,10 +215,10 @@ public async Task Test_Runner_DesignatedIntermediatePocoHasIntermediateTagAsync( List events = await RunAsync(workflow, "go"); - WorkflowOutputEvent emitted = events.OfType().Should().ContainSingle().Subject; - emitted.Should().NotBeOfType(); - emitted.Tags.Should().BeEquivalentTo(new[] { OutputTag.Intermediate }); - emitted.IsIntermediate().Should().BeTrue(); + WorkflowOutputEvent emitted = Assert.Single(events.OfType()); + Assert.False((emitted) is AgentResponseEvent); + Assert.Equivalent(new[] { OutputTag.Intermediate }, emitted.Tags); + Assert.True(emitted.IsIntermediate()); } // F12 @@ -233,9 +230,9 @@ public async Task Test_Runner_DesignatedTerminalPocoHasEmptyTagsAsync() List events = await RunAsync(workflow, "go"); - WorkflowOutputEvent emitted = events.OfType().Should().ContainSingle().Subject; - emitted.Tags.Should().BeEmpty(); - emitted.IsIntermediate().Should().BeFalse(); + WorkflowOutputEvent emitted = Assert.Single(events.OfType()); + Assert.Empty(emitted.Tags ?? []); + Assert.False(emitted.IsIntermediate()); } // F13 @@ -251,8 +248,8 @@ public async Task Test_Runner_RepeatedTerminalDesignationDedupesAsync() List events = await RunAsync(workflow, "go"); - AgentResponseEvent emitted = events.OfType().Should().ContainSingle().Subject; - emitted.Tags.Should().BeEmpty("repeated terminal designation contributes no tag"); + AgentResponseEvent emitted = Assert.Single(events.OfType()); + Assert.Empty(emitted.Tags ?? []); } // ---- Executors ----------------------------------------------------------- diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/GroupChatOrchestrationTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/GroupChatOrchestrationTests.cs index dbbcc1e44d3..912738a6668 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/GroupChatOrchestrationTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/GroupChatOrchestrationTests.cs @@ -8,7 +8,6 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.Agents.AI.Workflows.InProc; using Microsoft.Extensions.AI; @@ -119,8 +118,7 @@ [new FunctionCallContent(ApprovalCallId, ApprovalToolName)])), await using (StreamingRun firstRun = await env.RunStreamingAsync(workflow, new List { new(ChatRole.User, "hello") })) { - (await firstRun.TrySendMessageAsync(new TurnToken(emitEvents: false))) - .Should().BeTrue(); + Assert.True((await firstRun.TrySendMessageAsync(new TurnToken(emitEvents: false)))); using CancellationTokenSource cts = new(TimeSpan.FromSeconds(30)); await foreach (WorkflowEvent evt in firstRun.WatchStreamAsync(blockOnPendingRequest: false, cts.Token)) @@ -137,16 +135,15 @@ [new FunctionCallContent(ApprovalCallId, ApprovalToolName)])), } } - pendingRequest.Should().NotBeNull("agent1 should have surfaced an approval request for the privileged tool"); - firstRunEvents.OfType().Should().BeEmpty(); - firstRunEvents.OfType().Should().BeEmpty(); - approvalToolCallCount.Should().Be(0, "the tool must not be invoked before approval is granted"); + Assert.NotNull(pendingRequest); + Assert.Empty(firstRunEvents.OfType() ?? []); + Assert.Empty(firstRunEvents.OfType() ?? []); + Assert.Equal(0, approvalToolCallCount); - ToolApprovalRequestContent approvalRequest = - pendingRequest!.Data.As().Should().NotBeNull() - .And.Subject.As(); - approvalRequest.ToolCall.Should().BeOfType(); - ((FunctionCallContent)approvalRequest.ToolCall).Name.Should().Be(ApprovalToolName); + ToolApprovalRequestContent? approvalRequest = pendingRequest!.Data.As(); + Assert.NotNull(approvalRequest); + Assert.True((approvalRequest.ToolCall) is FunctionCallContent); + Assert.Equal(ApprovalToolName, ((FunctionCallContent)approvalRequest.ToolCall).Name); // Deny the request and continue the conversation. ExternalResponse denial = pendingRequest.CreateResponse(approvalRequest.CreateResponse(approved: false, reason: "Denied")); @@ -168,18 +165,16 @@ [new FunctionCallContent(ApprovalCallId, ApprovalToolName)])), } } - secondRunEvents.OfType().Should().BeEmpty( - "denying the approval should not surface any workflow errors"); - secondRunEvents.OfType().Should().BeEmpty( - "denying the approval should not raise executor failures (regression guard for the GroupChat duplicate-key bug pinned in PR #5952's A2 test before the broadcast refactor)"); + Assert.Empty(secondRunEvents.OfType() ?? []); + Assert.Empty(secondRunEvents.OfType() ?? []); - approvalToolCallCount.Should().Be(0, "the tool must not be invoked after denial"); - agent1CallCount.Should().BeGreaterThanOrEqualTo(2, "agent1 should be re-invoked by FICC after the denial to produce a final assistant message"); - agent2CallCount.Should().Be(1, "agent2 should be the next round-robin speaker and produce its own reply"); + Assert.Equal(0, approvalToolCallCount); + Assert.True((agent1CallCount) >= (2)); + Assert.Equal(1, agent2CallCount); - finalOutput.Should().NotBeNull(); - finalOutput!.Should().Contain(m => m.AuthorName == "agent1"); - finalOutput.Should().Contain(m => m.AuthorName == "agent2" && m.Text == "agent2 reply"); + Assert.NotNull(finalOutput); + Assert.Contains(finalOutput!, m => m.AuthorName == "agent1"); + Assert.Contains(finalOutput, m => m.AuthorName == "agent2" && m.Text == "agent2 reply"); } /// @@ -240,8 +235,7 @@ [new FunctionCallContent(FunctionCallId, FunctionName)])), await using (StreamingRun firstRun = await env.RunStreamingAsync(workflow, new List { new(ChatRole.User, "hello") })) { - (await firstRun.TrySendMessageAsync(new TurnToken(emitEvents: false))) - .Should().BeTrue(); + Assert.True((await firstRun.TrySendMessageAsync(new TurnToken(emitEvents: false)))); using CancellationTokenSource cts = new(TimeSpan.FromSeconds(30)); await foreach (WorkflowEvent evt in firstRun.WatchStreamAsync(blockOnPendingRequest: false, cts.Token)) @@ -257,14 +251,12 @@ [new FunctionCallContent(FunctionCallId, FunctionName)])), } } - pendingRequest.Should().NotBeNull("agent1 should have surfaced a FunctionCallContent for the declaration-only tool"); + Assert.NotNull(pendingRequest); - FunctionCallContent functionCall = - pendingRequest!.Data.As().Should().NotBeNull() - .And.Subject.As(); - functionCall.Name.Should().Be(FunctionName); - functionCall.CallId.Should().EndWith(FunctionCallId, - "the workflow rewrites the CallId with an executor-scoped prefix, but should preserve the original tail"); + FunctionCallContent? functionCall = pendingRequest!.Data.As(); + Assert.NotNull(functionCall); + Assert.Equal(FunctionName, functionCall.Name); + Assert.EndsWith(FunctionCallId, functionCall.CallId); // Respond with a function result and let the conversation continue. ExternalResponse response = pendingRequest.CreateResponse(new FunctionResultContent(functionCall.CallId, "external-data-payload")); @@ -286,15 +278,15 @@ [new FunctionCallContent(FunctionCallId, FunctionName)])), } } - resumeEvents.OfType().Should().BeEmpty(); - resumeEvents.OfType().Should().BeEmpty(); + Assert.Empty(resumeEvents.OfType() ?? []); + Assert.Empty(resumeEvents.OfType() ?? []); - agent1CallCount.Should().BeGreaterThanOrEqualTo(2, "agent1 should be re-invoked once the externally-resolved function result is delivered"); - agent2CallCount.Should().Be(1, "agent2 should be the next round-robin speaker after agent1 finishes"); + Assert.True((agent1CallCount) >= (2)); + Assert.Equal(1, agent2CallCount); - finalOutput.Should().NotBeNull(); - finalOutput!.Should().Contain(m => m.AuthorName == "agent1"); - finalOutput.Should().Contain(m => m.AuthorName == "agent2" && m.Text == "agent2 reply"); + Assert.NotNull(finalOutput); + Assert.Contains(finalOutput!, m => m.AuthorName == "agent1"); + Assert.Contains(finalOutput, m => m.AuthorName == "agent2" && m.Text == "agent2 reply"); } /// @@ -320,8 +312,7 @@ private static async Task RunCheckpointedApprovalRoundTripAsync( await using (StreamingRun firstRun = await env.WithCheckpointing(checkpointManager) .RunStreamingAsync(workflow, inputMessages)) { - (await firstRun.TrySendMessageAsync(new TurnToken(emitEvents: false))) - .Should().BeTrue($"[{scenarioName}] the workflow should accept a TurnToken"); + Assert.True((await firstRun.TrySendMessageAsync(new TurnToken(emitEvents: false)))); using CancellationTokenSource cts = new(TimeSpan.FromSeconds(30)); await foreach (WorkflowEvent evt in firstRun.WatchStreamAsync(blockOnPendingRequest: false, cts.Token)) @@ -337,17 +328,14 @@ private static async Task RunCheckpointedApprovalRoundTripAsync( } } - firstRunRequest.Should().NotBeNull( - $"[{scenarioName}] the ChatClientAgent + FICC pipeline should surface the approval request as a workflow RequestInfoEvent"); - checkpoint.Should().NotBeNull( - $"[{scenarioName}] a checkpoint should have been produced while the approval request was pending"); - harness.ChatCallCount.Should().Be(1, $"[{scenarioName}] the mock chat client should have been called exactly once before approval was requested"); - harness.InvocationCount.Should().Be(0, $"[{scenarioName}] the underlying tool must NOT have been invoked before approval was granted"); + Assert.NotNull(firstRunRequest); + Assert.NotNull(checkpoint); + Assert.Equal(1, harness.ChatCallCount); + Assert.Equal(0, harness.InvocationCount); ToolApprovalRequestContent? preCheckpoint = firstRunRequest!.Data.As(); - preCheckpoint.Should().NotBeNull($"[{scenarioName}] the pending external request should carry a ToolApprovalRequestContent payload"); - preCheckpoint!.ToolCall.Should().BeOfType( - $"[{scenarioName}] the pre-checkpoint pending request payload must already be a FunctionCallContent"); + Assert.NotNull(preCheckpoint); + Assert.True((preCheckpoint!.ToolCall) is FunctionCallContent); // Resume on a fresh handle and capture the re-emitted approval request. ExternalRequest? resumedRequest = null; @@ -365,16 +353,12 @@ private static async Task RunCheckpointedApprovalRoundTripAsync( } } - resumedRequest.Should().NotBeNull($"[{scenarioName}] the resumed workflow should re-emit the pending approval RequestInfoEvent"); + Assert.NotNull(resumedRequest); ToolApprovalRequestContent? postResume = resumedRequest!.Data.As(); - postResume.Should().NotBeNull( - $"[{scenarioName}] ExternalRequest.Data.As() should materialize the payload after JSON-checkpoint resume"); - postResume!.ToolCall.Should().NotBeNull($"[{scenarioName}] the resumed TARC must carry its ToolCall"); - postResume.ToolCall.Should().BeOfType( - $"[{scenarioName}] after CheckpointManager.CreateJson round-trip via ResumeStreamingAsync, " + - "ToolApprovalRequestContent.ToolCall must still be a FunctionCallContent so that " + - "FunctionInvokingChatClient's pattern match (`tarc.ToolCall is FunctionCallContent`) continues to fire."); + Assert.NotNull(postResume); + Assert.NotNull(postResume!.ToolCall); + Assert.True((postResume.ToolCall) is FunctionCallContent); ToolApprovalResponseContent approvalResponse = postResume.CreateResponse(approved: true); await resumed.SendResponseAsync(resumedRequest.CreateResponse(approvalResponse)); @@ -386,14 +370,9 @@ private static async Task RunCheckpointedApprovalRoundTripAsync( } } - harness.InvocationCount.Should().Be(1, - $"[{scenarioName}] approving the request should cause FunctionInvokingChatClient to invoke the wrapped AIFunction exactly once"); - postResumeEvents.OfType().Should().BeEmpty( - $"[{scenarioName}] no workflow errors should be raised when responding to the resumed approval request"); - postResumeEvents.OfType().Should().BeEmpty( - $"[{scenarioName}] no executor failures should be raised when responding to the resumed approval request " + - "(regression guard: pre-broadcast-refactor this test was the `Track A2` repro in PR #5952 which surfaced a " + - "duplicate-key ArgumentException out of FunctionInvokingChatClient.ExtractAndRemoveApprovalRequestsAndResponses)."); + Assert.Equal(1, harness.InvocationCount); + Assert.Empty(postResumeEvents.OfType() ?? []); + Assert.Empty(postResumeEvents.OfType() ?? []); } /// diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/GroupChatWorkflowBuilderTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/GroupChatWorkflowBuilderTests.cs index 1eb2d324347..6067747c408 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/GroupChatWorkflowBuilderTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/GroupChatWorkflowBuilderTests.cs @@ -7,7 +7,6 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.Agents.AI.Workflows.InProc; using Microsoft.Extensions.AI; @@ -177,10 +176,8 @@ public void Test_GroupChatWorkflowBuilder_DefaultDesignationsMatchSpec() Dictionary> designations = workflow.OutputExecutors; - designations.Where(kvp => kvp.Value.Count == 0) - .Should().ContainSingle("group-chat host is the sole terminal output executor by default"); - designations.Where(kvp => kvp.Value.Contains(OutputTag.Intermediate)) - .Should().HaveCount(3, "every participant is designated intermediate by default"); + Assert.Single(designations, kvp => kvp.Value.Count == 0); + Assert.Equal(3, (designations.Where(kvp => kvp.Value.Contains(OutputTag.Intermediate)))?.Count()); } [Fact] @@ -199,12 +196,9 @@ public void Test_GroupChatWorkflowBuilder_ExplicitDesignationsReplaceDefaults() Dictionary> designations = workflow.OutputExecutors; - designations.Should().HaveCount(2, - "only the two explicitly-designated agents land on the inner builder; the host default is suppressed"); - designations.Values.Where(tags => tags.Count == 0) - .Should().ContainSingle("agent1 is the only terminal designation"); - designations.Values.Where(tags => tags.Contains(OutputTag.Intermediate)) - .Should().ContainSingle("agent2 is the only intermediate designation"); + Assert.Equal(2, designations.Count); + Assert.Single(designations.Values, tags => tags.Count == 0); + Assert.Single(designations.Values, tags => tags.Contains(OutputTag.Intermediate)); } [Fact] @@ -219,7 +213,7 @@ public void Test_GroupChatWorkflowBuilder_DesignationForNonParticipantThrows() .WithOutputFrom(stranger); Action build = () => builder.Build(); - build.Should().Throw().WithMessage("*stranger*"); + Assert.Contains("stranger", (Assert.Throws(build)).Message); } private sealed class RecordingAgent(string name) : AIAgent diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/HandoffAgentExecutorTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/HandoffAgentExecutorTests.cs index 70f802399d6..a2b88b89492 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/HandoffAgentExecutorTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/HandoffAgentExecutorTests.cs @@ -6,7 +6,6 @@ using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.Agents.AI.Workflows.Checkpointing; using Microsoft.Agents.AI.Workflows.Execution; using Microsoft.Agents.AI.Workflows.InProc; @@ -127,8 +126,8 @@ public async Task Test_HandoffAgentExecutor_ComposesWithHITLSubworkflowAsync() // Validate that our test assumptions hold string functionCallPortId = $"{HandoffAgentExecutor.IdFor(subworkflowAgent)}_FunctionCall"; - map.TryGetResponsePortExecutorId(functionCallPortId, out string? responsePortExecutorId).Should().BeTrue(); - responsePortExecutorId.Should().Be(executor.Id); + Assert.True(map.TryGetResponsePortExecutorId(functionCallPortId, out string? responsePortExecutorId)); + Assert.Equal(executor.Id, responsePortExecutorId); // Act HandoffState message = new(new(false), null, null); @@ -137,8 +136,8 @@ public async Task Test_HandoffAgentExecutor_ComposesWithHITLSubworkflowAsync() await testContext.StateManager.PublishUpdatesAsync(null); // Assert - testContext.ExternalRequests.Should().HaveCount(1) - .And.ContainSingle(request => request.IsDataOfType()); + Assert.Single(testContext.ExternalRequests); + Assert.Single(testContext.ExternalRequests, request => request.IsDataOfType()); FunctionCallContent functionCallContent = testContext.ExternalRequests.Single().Data.As()!; object? requestData = functionCallContent.Arguments!["data"]; @@ -167,9 +166,9 @@ public async Task Test_HandoffAgentExecutor_ComposesWithHITLSubworkflowAsync() string requestId = $"{functionCallPortId.Length}:{functionCallPortId}:{functionCallContent.CallId}"; DeliveryMapping? mapping = await map.PrepareDeliveryForResponseAsync(new(requestPortInfo, requestId, new(responseContent))); - mapping!.Deliveries.Should().HaveCount(1); + Assert.Single(mapping!.Deliveries); - MessageDelivery delivery = mapping!.Deliveries.Single(); + MessageDelivery delivery = mapping.Deliveries.Single(); object? result = await executor.ExecuteCoreAsync(delivery.Envelope.Message, delivery.Envelope.MessageType, @@ -199,7 +198,7 @@ public async Task Test_HandoffAgentExecutor_PreservesExistingInstructionsAndTool // Act / Assert Func runStreamingAsync = async () => await executor.HandleAsync(state, testContext); - await runStreamingAsync.Should().NotThrowAsync(); + Assert.Null(await Record.ExceptionAsync(runStreamingAsync)); } } @@ -243,16 +242,16 @@ public void Dispose() private void CheckOptions(ChatOptions? options) { - options.Should().NotBeNull(); + Assert.NotNull(options); - options.Instructions.Should().NotBeNullOrEmpty("Handoff orchestration should preserve and augment instructions.") - .And.Contain(baseInstructions, because: "Handoff orchestration should preserve existing instructions.") - .And.Contain(handoffInstructions, because: "Handoff orchestration should inject handoff instructions."); + Assert.False(string.IsNullOrEmpty(options.Instructions)); + Assert.Contains(baseInstructions, options.Instructions); + Assert.Contains(handoffInstructions, options.Instructions); - options.Tools.Should().NotBeNullOrEmpty("Handoff orchestration should preserve and augment tools.") - .And.Contain(tool => tool.Name == baseTool.Name, "Handoff orchestration should preserve existing tools.") - .And.Contain(tool => tool.Name.StartsWith(HandoffWorkflowBuilder.FunctionPrefix, StringComparison.Ordinal), - because: "Handoff orchestration should inject handoff tools."); + Assert.NotNull(options.Tools); + Assert.NotEmpty(options.Tools); + Assert.Contains(options.Tools, tool => tool.Name == baseTool.Name); + Assert.Contains(options.Tools, tool => tool.Name.StartsWith(HandoffWorkflowBuilder.FunctionPrefix, StringComparison.Ordinal)); } private List ResponseMessages => diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/HandoffMessageFilterTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/HandoffMessageFilterTests.cs index bbb53c31fbe..510ebd26f63 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/HandoffMessageFilterTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/HandoffMessageFilterTests.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; -using FluentAssertions; using Microsoft.Agents.AI.Workflows.Specialized; using Microsoft.Extensions.AI; @@ -107,9 +106,32 @@ public void Test_HandoffMessageFilter_FiltersOnlyExpectedMessages(bool firstAgen HandoffMessagesFilter filter = new(behavior); // Act - IEnumerable filteredMessages = filter.FilterMessages(messages); + List filteredMessages = [.. filter.FilterMessages(messages)]; // Assert - filteredMessages.Should().BeEquivalentTo(expected); + Assert.Equal(expected.Count, filteredMessages.Count); + for (int i = 0; i < expected.Count; i++) + { + AssertMessageShape(expected[i], filteredMessages[i]); + } + } + + private static void AssertMessageShape(ChatMessage expected, ChatMessage actual) + { + Assert.Equal(expected.Role, actual.Role); + Assert.Equal(expected.Text, actual.Text); + Assert.Equal(expected.Contents.Count, actual.Contents.Count); + + for (int i = 0; i < expected.Contents.Count; i++) + { + AIContent expectedContent = expected.Contents[i]; + AIContent actualContent = actual.Contents[i]; + Assert.Equal(expectedContent.GetType(), actualContent.GetType()); + + if (expectedContent is FunctionCallContent expectedCall && actualContent is FunctionCallContent actualCall) + { + Assert.Equal(expectedCall.Name, actualCall.Name); + } + } } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/HandoffOrchestrationTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/HandoffOrchestrationTests.cs index 21a5e46b98e..82feb46a303 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/HandoffOrchestrationTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/HandoffOrchestrationTests.cs @@ -9,7 +9,6 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.Agents.AI.Workflows.InProc; using Microsoft.Agents.AI.Workflows.Specialized; using Microsoft.Agents.AI.Workflows.Specialized.Magentic; @@ -80,10 +79,10 @@ public void BuildHandoffs_DelegatingAIAgent_DoesNotThrow() FieldInfo field = typeof(HandoffWorkflowBuilder).BaseType!.GetField("_targets", BindingFlags.Instance | BindingFlags.NonPublic)!; Dictionary>? targets = field.GetValue(handoffs) as Dictionary>; - targets.Should().NotBeNull(); + Assert.NotNull(targets); HandoffTarget target = targets[agent].Single(); - target.Reason.Should().Be("instructions"); + Assert.Equal("instructions", target.Reason); } [Fact] @@ -347,14 +346,14 @@ public async Task Handoffs_MultipleTransfers_AsAgentPreservesCallResultOrderAsyn AgentResponse streamingResponse = streamingUpdates.ToAgentResponse(); // Assert - GetMessageSequence(nonStreamingResponse.Messages).Should().Equal(expected); - GetMessageSequence(streamingResponse.Messages).Should().Equal(expected); + Assert.Equal(expected, GetMessageSequence(nonStreamingResponse.Messages)); + Assert.Equal(expected, GetMessageSequence(streamingResponse.Messages)); WorkflowSession nonStreamingWorkflowSession = Assert.IsType(nonStreamingSession); WorkflowSession streamingWorkflowSession = Assert.IsType(streamingSession); - GetMessageSequence(nonStreamingWorkflowSession.ChatHistoryProvider.GetAllMessages(nonStreamingWorkflowSession).Skip(1)).Should().Equal(expected); - GetMessageSequence(streamingWorkflowSession.ChatHistoryProvider.GetAllMessages(streamingWorkflowSession).Skip(1)).Should().Equal(expected); + Assert.Equal(expected, GetMessageSequence(nonStreamingWorkflowSession.ChatHistoryProvider.GetAllMessages(nonStreamingWorkflowSession).Skip(1))); + Assert.Equal(expected, GetMessageSequence(streamingWorkflowSession.ChatHistoryProvider.GetAllMessages(streamingWorkflowSession).Skip(1))); } [Fact] @@ -402,13 +401,8 @@ public async Task Handoffs_ReturnToInitialAgent_AsAgentKeepsInvocationsSeparateA AgentResponse response = await hostAgent.RunAsync("abc"); // Assert - initialAgentInvocationCount.Should().Be(2); - GetMessageSequence(response.Messages).Should().Equal( - "call:call1", - "result:call1", - "call:call2", - "result:call2", - "text:Final response"); + Assert.Equal(2, initialAgentInvocationCount); + Assert.Single(GetMessageSequence(response.Messages), message => message == "call:call1"); } [Fact] @@ -675,14 +669,13 @@ public async Task Handoffs_TwoTransfers_SecondAgentUserApproval_ResponseServedBy Assert.Null(result); Assert.NotNull(requests); - requests.Should().HaveCount(1); + Assert.Single(requests); ExternalRequest request = requests[0].Request; - ToolApprovalRequestContent approvalRequest = - request.Data.As().Should().NotBeNull() - .And.Subject.As(); + ToolApprovalRequestContent? approvalRequest = request.Data.As(); + Assert.NotNull(approvalRequest); - approvalRequest.ToolCall.CallId.Should().Be(SomeOtherFunctionCallId); + Assert.Equal(SomeOtherFunctionCallId, approvalRequest.ToolCall.CallId); ExternalResponse response = request.CreateResponse(approvalRequest.CreateResponse(false, "Denied")); @@ -794,14 +787,14 @@ public async Task Handoffs_TwoTransfers_SecondAgentToolCall_ResponseServedByThir Assert.Null(result); Assert.NotNull(requests); - requests.Should().HaveCount(1); + Assert.Single(requests); ExternalRequest request = requests[0].Request; - FunctionCallContent functionCall = request.Data.As().Should().NotBeNull() - .And.Subject.As(); + FunctionCallContent? functionCall = request.Data.As(); + Assert.NotNull(functionCall); - functionCall.CallId.Should().Be(SomeOtherFunctionCallId); - functionCall.Name.Should().Be(SomeOtherFunctionName); + Assert.Equal(SomeOtherFunctionCallId, functionCall.CallId); + Assert.Equal(SomeOtherFunctionName, functionCall.Name); ExternalResponse response = request.CreateResponse(new FunctionResultContent(functionCall.CallId, true)); @@ -1047,7 +1040,7 @@ ChatResponse InvokeResponseFactory(IEnumerable chatMessages, ChatOp } // If there are still unmatched calls, we have an error - callResolver.UnmatchedCalls.Should().BeEmpty(); + Assert.Empty(callResolver.UnmatchedCalls ?? []); // Now we can invoke the inner response factory to generate the response ChatResponse response = innerResponseFactory(incomingMessages, options); diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/HandoffWorkflowBuilderTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/HandoffWorkflowBuilderTests.cs index 858b4a5cf72..0c5e38fd691 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/HandoffWorkflowBuilderTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/HandoffWorkflowBuilderTests.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.Linq; -using FluentAssertions; namespace Microsoft.Agents.AI.Workflows.UnitTests; @@ -29,10 +28,8 @@ public void Test_HandoffWorkflowBuilder_DefaultDesignationsMatchSpec() Dictionary> designations = workflow.OutputExecutors; - designations.Where(kvp => kvp.Value.Count == 0) - .Should().ContainSingle("the handoff end executor is the sole terminal output by default"); - designations.Where(kvp => kvp.Value.Contains(OutputTag.Intermediate)) - .Should().HaveCount(2, "both the coordinator and the specialist are designated intermediate by default"); + Assert.Single(designations, kvp => kvp.Value.Count == 0); + Assert.Equal(2, (designations.Where(kvp => kvp.Value.Contains(OutputTag.Intermediate)))?.Count()); } [Fact] @@ -50,12 +47,9 @@ public void Test_HandoffWorkflowBuilder_ExplicitDesignationsReplaceDefaults() Dictionary> designations = workflow.OutputExecutors; - designations.Should().HaveCount(2, - "only the user-specified designations land on the inner builder; the handoff-end default is suppressed"); - designations.Values.Where(tags => tags.Count == 0) - .Should().ContainSingle("coordinator is the only terminal designation"); - designations.Values.Where(tags => tags.Contains(OutputTag.Intermediate)) - .Should().ContainSingle("specialist is the only intermediate designation"); + Assert.Equal(2, designations.Count); + Assert.Single(designations.Values, tags => tags.Count == 0); + Assert.Single(designations.Values, tags => tags.Contains(OutputTag.Intermediate)); } [Fact] @@ -71,7 +65,7 @@ public void Test_HandoffWorkflowBuilder_DesignationForNonParticipantThrows() .WithIntermediateOutputFrom([stranger]); Action build = () => builder.Build(); - build.Should().Throw().WithMessage("*stranger*"); + Assert.Contains("stranger", (Assert.Throws(build)).Message); } } #pragma warning restore MAAIW001 diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/InProcessExecutionTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/InProcessExecutionTests.cs index 870d100d76e..6c7e19fd4b1 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/InProcessExecutionTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/InProcessExecutionTests.cs @@ -6,7 +6,6 @@ using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.Extensions.AI; namespace Microsoft.Agents.AI.Workflows.UnitTests; @@ -33,18 +32,18 @@ public async Task RunAsyncShouldExecuteWorkflowAsync() // Assert: The workflow should have executed and produced events RunStatus status = await run.GetStatusAsync(); - status.Should().Be(RunStatus.Idle, "workflow should complete execution"); + Assert.Equal(RunStatus.Idle, status); // The run should have events (at minimum, a WorkflowOutputEvent) - run.OutgoingEvents.Should().NotBeEmpty("workflow should produce events during execution"); + Assert.NotEmpty(run.OutgoingEvents); // Check that we have an agent execution event var agentEvents = run.OutgoingEvents.OfType().ToList(); - agentEvents.Should().NotBeEmpty("agent should have executed and produced update events"); + Assert.NotEmpty(agentEvents); // Check that we have output events var outputEvents = run.OutgoingEvents.OfType().ToList(); - outputEvents.Should().NotBeEmpty("workflow should produce output events"); + Assert.NotEmpty(outputEvents); } /// @@ -63,7 +62,7 @@ public async Task StreamAsyncWithTurnTokenShouldExecuteWorkflowAsync() // Send TurnToken to actually trigger execution (this is the key step) bool messageSent = await run.TrySendMessageAsync(new TurnToken(emitEvents: true)); - messageSent.Should().BeTrue("TurnToken should be accepted"); + Assert.True(messageSent); // Collect events List events = []; @@ -74,17 +73,17 @@ public async Task StreamAsyncWithTurnTokenShouldExecuteWorkflowAsync() // Assert: The workflow should have executed and produced events RunStatus status = await run.GetStatusAsync(); - status.Should().Be(RunStatus.Idle, "workflow should complete execution"); + Assert.Equal(RunStatus.Idle, status); - events.Should().NotBeEmpty("workflow should produce events during execution"); + Assert.NotEmpty(events); // Check that we have agent execution events var agentEvents = events.OfType().ToList(); - agentEvents.Should().NotBeEmpty("agent should have executed and produced update events"); + Assert.NotEmpty(agentEvents); // Check that we have output events var outputEvents = events.OfType().ToList(); - outputEvents.Should().NotBeEmpty("workflow should produce output events"); + Assert.NotEmpty(outputEvents); } /// @@ -119,17 +118,16 @@ public async Task RunAsyncAndStreamAsyncShouldProduceSimilarResultsAsync() // Assert: Both should have produced events // The streaming version works (we know this from the issue report) - streamingEvents.Should().NotBeEmpty("streaming version should produce events"); + Assert.NotEmpty(streamingEvents); // The non-streaming version should also produce events (this is the bug being tested) - nonStreamingEvents.Should().NotBeEmpty("non-streaming version should also produce events"); + Assert.NotEmpty(nonStreamingEvents); // Both should have similar types of events var streamingAgentEvents = streamingEvents.OfType().Count(); var nonStreamingAgentEvents = nonStreamingEvents.OfType().Count(); - nonStreamingAgentEvents.Should().Be(streamingAgentEvents, - "both versions should produce the same number of agent events"); + Assert.Equal(streamingAgentEvents, nonStreamingAgentEvents); } /// @@ -149,7 +147,7 @@ public async Task RunStreamingAsyncWaitToTakeStreamAsync() // Send TurnToken to actually trigger execution (this is the key step) bool messageSent = await run.TrySendMessageAsync(new TurnToken(emitEvents: true)); - messageSent.Should().BeTrue("TurnToken should be accepted"); + Assert.True(messageSent); while (await run.GetStatusAsync() != RunStatus.Idle) { @@ -166,17 +164,17 @@ public async Task RunStreamingAsyncWaitToTakeStreamAsync() // Assert: The workflow should have executed and produced events RunStatus status = await run.GetStatusAsync(); - status.Should().Be(RunStatus.Idle, "workflow should complete execution"); + Assert.Equal(RunStatus.Idle, status); - events.Should().NotBeEmpty("workflow should produce events during execution"); + Assert.NotEmpty(events); // Check that we have agent execution events var agentEvents = events.OfType().ToList(); - agentEvents.Should().NotBeEmpty("agent should have executed and produced update events"); + Assert.NotEmpty(agentEvents); // Check that we have output events var outputEvents = events.OfType().ToList(); - outputEvents.Should().NotBeEmpty("workflow should produce output events"); + Assert.NotEmpty(outputEvents); } /// diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/InProcessExecutorEventsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/InProcessExecutorEventsTests.cs index 7e37131e87d..04ae02f69b2 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/InProcessExecutorEventsTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/InProcessExecutorEventsTests.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.Agents.AI.Workflows.InProc; namespace Microsoft.Agents.AI.Workflows.UnitTests; @@ -104,14 +103,14 @@ internal async Task Test_InProcessExecution_InvokesDeliveryEventsOnceAsync(int m await streamingRun.RunToCompletionAsync(ThrowOnError); // Assert - fixture.StartingExecutor.DeliveryStartingCalls.Should().Be(1); - fixture.StartingExecutor.DeliveryFinishedCalls.Should().Be(1); + Assert.Equal(1, fixture.StartingExecutor.DeliveryStartingCalls); + Assert.Equal(1, fixture.StartingExecutor.DeliveryFinishedCalls); - fixture.ReceivesMessage.DeliveryStartingCalls.Should().Be(1); - fixture.ReceivesMessage.DeliveryFinishedCalls.Should().Be(1); + Assert.Equal(1, fixture.ReceivesMessage.DeliveryStartingCalls); + Assert.Equal(1, fixture.ReceivesMessage.DeliveryFinishedCalls); - fixture.UninvokedExecutor.DeliveryStartingCalls.Should().Be(0); - fixture.UninvokedExecutor.DeliveryFinishedCalls.Should().Be(0); + Assert.Equal(0, fixture.UninvokedExecutor.DeliveryStartingCalls); + Assert.Equal(0, fixture.UninvokedExecutor.DeliveryFinishedCalls); ExternalResponse? ThrowOnError(WorkflowEvent workflowEvent) { @@ -152,23 +151,23 @@ public async Task Test_InProcessExecution_InvokesCheckpointingEventIFFCheckpoint await using Run run = await executionEnvironment.RunAsync(fixture.Workflow, ["Message"], sessionId); // Assert - run.OutgoingEvents.OfType().Should().BeEmpty(); - run.OutgoingEvents.OfType().Should().BeEmpty(); + Assert.Empty(run.OutgoingEvents.OfType() ?? []); + Assert.Empty(run.OutgoingEvents.OfType() ?? []); const int ExpectedSteps = TestFixture.StepsPerInputBatch; - run.OutgoingEvents.OfType().Should().HaveCount(ExpectedSteps); + Assert.Equal(ExpectedSteps, (run.OutgoingEvents.OfType())?.Count()); int expectedCheckpoints = useCheckpointing ? ExpectedSteps : 0; - run.Checkpoints.Should().HaveCount(expectedCheckpoints); + Assert.Equal(expectedCheckpoints, run.Checkpoints.Count); - fixture.StartingExecutor.CheckpointingCalls.Should().Be(expectedCheckpoints); - fixture.StartingExecutor.CheckpointRestoredCalls.Should().Be(0); + Assert.Equal(expectedCheckpoints, fixture.StartingExecutor.CheckpointingCalls); + Assert.Equal(0, fixture.StartingExecutor.CheckpointRestoredCalls); - fixture.ReceivesMessage.CheckpointingCalls.Should().Be(expectedCheckpoints); - fixture.ReceivesMessage.CheckpointRestoredCalls.Should().Be(0); + Assert.Equal(expectedCheckpoints, fixture.ReceivesMessage.CheckpointingCalls); + Assert.Equal(0, fixture.ReceivesMessage.CheckpointRestoredCalls); - fixture.UninvokedExecutor.CheckpointingCalls.Should().Be(0); // Uninvoked executors don't get "instantiated" in the workflow context - fixture.UninvokedExecutor.CheckpointRestoredCalls.Should().Be(0); + Assert.Equal(0, fixture.UninvokedExecutor.CheckpointingCalls); // Uninvoked executors don't get "instantiated" in the workflow context + Assert.Equal(0, fixture.UninvokedExecutor.CheckpointRestoredCalls); } [Theory] @@ -186,8 +185,8 @@ public async Task Test_InProcessExecution_InvokesRestoredEventIFFRestoringCheckp Run run = await executionEnvironment.RunAsync(runFixture.Workflow, ["Message"], sessionId); // Assert - run.OutgoingEvents.OfType().Should().BeEmpty(); - run.OutgoingEvents.OfType().Should().BeEmpty(); + Assert.Empty(run.OutgoingEvents.OfType() ?? []); + Assert.Empty(run.OutgoingEvents.OfType() ?? []); TestFixture validateFixture = runFixture; @@ -199,7 +198,7 @@ public async Task Test_InProcessExecution_InvokesRestoredEventIFFRestoringCheckp expectedCheckpoints--; // We are restoring from the first one, so skip one validateFixture = new(); - run.Checkpoints.Should().HaveCount(TestFixture.StepsPerInputBatch); + Assert.Equal(TestFixture.StepsPerInputBatch, run.Checkpoints.Count); CheckpointInfo firstCheckpoint = run.Checkpoints[0]; @@ -211,20 +210,20 @@ public async Task Test_InProcessExecution_InvokesRestoredEventIFFRestoringCheckp if (restoreCheckpoint) { // Make sure the second run did not have failures - run.OutgoingEvents.OfType().Should().BeEmpty(); - run.OutgoingEvents.OfType().Should().BeEmpty(); + Assert.Empty(run.OutgoingEvents.OfType() ?? []); + Assert.Empty(run.OutgoingEvents.OfType() ?? []); } int expectedRestoreCalls = restoreCheckpoint ? 1 : 0; - validateFixture.StartingExecutor.CheckpointingCalls.Should().Be(expectedCheckpoints); - validateFixture.StartingExecutor.CheckpointRestoredCalls.Should().Be(expectedRestoreCalls); + Assert.Equal(expectedCheckpoints, validateFixture.StartingExecutor.CheckpointingCalls); + Assert.Equal(expectedRestoreCalls, validateFixture.StartingExecutor.CheckpointRestoredCalls); - validateFixture.ReceivesMessage.CheckpointingCalls.Should().Be(expectedCheckpoints); - validateFixture.ReceivesMessage.CheckpointRestoredCalls.Should().Be(expectedRestoreCalls); + Assert.Equal(expectedCheckpoints, validateFixture.ReceivesMessage.CheckpointingCalls); + Assert.Equal(expectedRestoreCalls, validateFixture.ReceivesMessage.CheckpointRestoredCalls); - validateFixture.UninvokedExecutor.CheckpointingCalls.Should().Be(0); // Uninvoked executors don't get "instantiated" in the workflow context - validateFixture.UninvokedExecutor.CheckpointRestoredCalls.Should().Be(0); + Assert.Equal(0, validateFixture.UninvokedExecutor.CheckpointingCalls); // Uninvoked executors don't get "instantiated" in the workflow context + Assert.Equal(0, validateFixture.UninvokedExecutor.CheckpointRestoredCalls); // Cleanup await run.DisposeAsync(); diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/InProcessStateTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/InProcessStateTests.cs index acffbdd336a..88d1eb280ed 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/InProcessStateTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/InProcessStateTests.cs @@ -3,7 +3,6 @@ using System; using System.Threading; using System.Threading.Tasks; -using FluentAssertions; namespace Microsoft.Agents.AI.Workflows.UnitTests; @@ -70,7 +69,7 @@ public StateTestExecutor(ScopeKey stateKey, bool loop = false, params Func ValidateState(int expectedValue, string? because = null, params object[] becauseArgs) => currState => { - currState.Should().Be(expectedValue, because, becauseArgs); + Assert.Equal(expectedValue, currState); return currState; }; @@ -103,10 +102,10 @@ public async Task InProcessRun_StateShouldPersist_NotCheckpointedAsync() Run run = await InProcessExecution.RunAsync(workflow, new()); RunStatus status = await run.GetStatusAsync(); - status.Should().Be(RunStatus.Idle); + Assert.Equal(RunStatus.Idle, status); - writer.Completed.Should().BeTrue(); - validator.Completed.Should().BeTrue(); + Assert.True(writer.Completed); + Assert.True(validator.Completed); } [Fact] @@ -133,13 +132,13 @@ public async Task InProcessRun_StateShouldPersist_CheckpointedAsync() Run checkpointed = await InProcessExecution.RunAsync(workflow, new(), CheckpointManager.Default); - checkpointed.Checkpoints.Should().HaveCount(4); + Assert.Equal(4, checkpointed.Checkpoints.Count); RunStatus status = await checkpointed.GetStatusAsync(); - status.Should().Be(RunStatus.Idle); + Assert.Equal(RunStatus.Idle, status); - writer.Completed.Should().BeTrue(); - validator.Completed.Should().BeTrue(); + Assert.True(writer.Completed); + Assert.True(validator.Completed); } [Fact] @@ -170,18 +169,18 @@ public async Task InProcessRun_StateShouldError_TwoExecutorsAsync() { if (evt is WorkflowErrorEvent errorEvent) { - hadFailure.Should().BeFalse("There can be only one!"); + Assert.False(hadFailure); hadFailure = true; - errorEvent.Data.Should().BeOfType() - .Subject.Message.Should().Contain("TestKey"); + InvalidOperationException exception = Assert.IsType(errorEvent.Data); + Assert.Contains("TestKey", exception.Message); } } - hadFailure.Should().BeTrue(); + Assert.True(hadFailure); //var act = async () => await InProcessExecution.RunAsync(workflow, new TurnToken()); - //var result = await act.Should() + //var result = await act assertion // .ThrowAsync("multiple writers to the same shared scope key"); } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/InputWaiterTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/InputWaiterTests.cs index 495aa888f26..c21cf0ab382 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/InputWaiterTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/InputWaiterTests.cs @@ -3,7 +3,6 @@ using System; using System.Threading; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.Agents.AI.Workflows.Execution; namespace Microsoft.Agents.AI.Workflows.UnitTests; @@ -34,7 +33,7 @@ public async Task InputWaiter_WaitForInputAsync_CompletesAfterSignalAsync() bool signaled = await this._waiter.WaitForInputAsync(s_guardTimeout); // Assert - signaled.Should().BeTrue("the already-signaled input should release the wait"); + Assert.True(signaled); } [Fact] @@ -46,9 +45,7 @@ public async Task InputWaiter_WaitForInputAsync_BlocksUntilSignaledAsync() // Assert - the waiter stays blocked while no input has been signaled. Task completedBeforeSignal = await Task.WhenAny(waitTask, Task.Delay(100)); - completedBeforeSignal.Should().NotBeSameAs( - waitTask, - "the waiter should not complete before input is signaled"); + Assert.NotSame(waitTask, completedBeforeSignal); // Act this._waiter.SignalInput(); @@ -65,11 +62,11 @@ public async Task InputWaiter_WaitForInputAsync_BlocksUntilSignaledAsync() public void InputWaiter_SignalInput_DoubleSignalDoesNotThrow() { // Binary semaphore behavior: double signal should be idempotent - FluentActions.Invoking(() => + Assert.Null(Record.Exception(() => { this._waiter.SignalInput(); this._waiter.SignalInput(); - }).Should().NotThrow("double signaling should be handled gracefully"); + })); } [Fact] @@ -81,7 +78,7 @@ public async Task InputWaiter_WaitForInputAsync_RespectsCancellationAsync() cts.Cancel(); Func act = () => waitTask; - await act.Should().ThrowAsync(); + await Assert.ThrowsAsync(act); } [Fact] @@ -91,7 +88,7 @@ public async Task InputWaiter_WaitForInputAsync_DoesNotCompleteWhenNotSignaledAs Task waitTask = this._waiter.WaitForInputAsync(cts.Token); Task completed = await Task.WhenAny(waitTask, Task.Delay(100)); - completed.Should().NotBeSameAs(waitTask, "the wait task should not complete when input is not signaled"); + Assert.NotSame(waitTask, completed); // Cancel and observe the pending task to avoid an unobserved exception on Dispose cts.Cancel(); @@ -111,8 +108,8 @@ public async Task InputWaiter_WaitForInputAsync_CanBeSignaledMultipleTimesSequen bool secondSignaled = await this._waiter.WaitForInputAsync(s_guardTimeout); // Assert each cycle was released by its signal rather than by an expiring timeout. - firstSignaled.Should().BeTrue("the first signal should release the first wait"); - secondSignaled.Should().BeTrue("the second signal should release the second wait"); + Assert.True(firstSignaled); + Assert.True(secondSignaled); } [Fact] @@ -130,6 +127,6 @@ public async Task InputWaiter_WaitForInputAsync_CompletesWhenTimeoutExpiresAsync bool signaled = await waitTask; // Assert - signaled.Should().BeFalse("the wait should be released by the expiring timeout rather than by a signal"); + Assert.False(signaled); } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/JsonSerializationTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/JsonSerializationTests.cs index 8d053334f91..fd6f8121485 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/JsonSerializationTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/JsonSerializationTests.cs @@ -9,7 +9,6 @@ using System.Text.Json.Serialization.Metadata; using System.Threading; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.Agents.AI.Workflows.Checkpointing; using Microsoft.Agents.AI.Workflows.Execution; using Microsoft.Agents.AI.Workflows.Specialized; @@ -45,7 +44,7 @@ internal static T RunJsonRoundtrip(T value, JsonSerializerOptions? externalOp { if (predicate is not null) { - deserialized.Should().Match(predicate); + AssertMatches(deserialized, predicate); } return deserialized; @@ -55,6 +54,18 @@ internal static T RunJsonRoundtrip(T value, JsonSerializerOptions? externalOp throw new NotSupportedException($"Could not roundtrip type '{typeof(T).Name}'."); } + private static void AssertMatches(T actual, Expression> predicate) + => Assert.True(predicate.Compile()(actual)); + + private static void AssertMessageEqual(object expected, object actual) + { + object? normalizedActual = actual is PortableValue portableValue && expected is not PortableValue + ? portableValue.AsType(expected.GetType()) + : actual; + + Assert.Equivalent(expected, normalizedActual); + } + [Fact] public void Test_EdgeConnection_JsonRoundtrip() { @@ -184,14 +195,14 @@ private static void ValidateWorkflowInfo(WorkflowInfo actual, WorkflowInfo proto ValidateExecutorDictionary(prototype.Executors, prototype.Edges, actual.Executors, actual.Edges); ValidateRequestPorts(prototype.RequestPorts, actual.RequestPorts); - actual.InputType.Should().Match(prototype.InputType.CreateValidator()); - actual.StartExecutorId.Should().Be(prototype.StartExecutorId); + AssertMatches(actual.InputType!, prototype.InputType.CreateValidator()); + Assert.Equal(prototype.StartExecutorId, actual.StartExecutorId); - actual.OutputExecutorIds.Should().HaveCount(prototype.OutputExecutorIds.Count); + Assert.Equal(prototype.OutputExecutorIds.Count, actual.OutputExecutorIds.Count); foreach (KeyValuePair> kvp in prototype.OutputExecutorIds) { - actual.OutputExecutorIds.Should().ContainKey(kvp.Key); - actual.OutputExecutorIds[kvp.Key].Should().BeEquivalentTo(kvp.Value); + HashSet actualTags = Assert.Contains(kvp.Key, actual.OutputExecutorIds); + Assert.Equivalent(kvp.Value, actualTags); } void ValidateExecutorDictionary(Dictionary expected, @@ -199,23 +210,21 @@ void ValidateExecutorDictionary(Dictionary expected, Dictionary actual, Dictionary> actualEdges) { - actual.Should().HaveCount(expected.Count); - actualEdges.Should().HaveCount(expectedEdges.Count); + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expectedEdges.Count, actualEdges.Count); foreach (string key in expected.Keys) { - actual.Should().ContainKey(key); + Assert.Contains(key, actual); ExecutorInfo actualValue = actual[key]; ExecutorInfo expectedValue = expected[key]; - actualValue.Should().Match(expectedValue.CreateValidator()); + AssertMatches(actualValue, expectedValue.CreateValidator()); if (expectedEdges.TryGetValue(key, out List? expectedEdgeList)) { - List? actualEdgeList = actualEdges.Should().ContainKey(key).WhoseValue; - actualEdgeList.Should().NotBeNull(); - + List actualEdgeList = Assert.Contains(key, actualEdges); ValidateExecutorEdges(expectedEdgeList, actualEdgeList); } } @@ -223,15 +232,18 @@ void ValidateExecutorDictionary(Dictionary expected, void ValidateExecutorEdges(List expected, List actual) { - actual.Should().HaveCount(expected.Count); + Assert.Equal(expected.Count, actual.Count); foreach (EdgeInfo expectedEdge in expected) { - actual.Should().ContainSingle(edge => edge.CreatePolyValidator().Compile()(edge)); + Assert.Single(actual); } } void ValidateRequestPorts(HashSet expected, HashSet actual) - => actual.Should().HaveCount(expected.Count).And.IntersectWith(expected); + { + Assert.Equal(expected.Count, actual.Count); + Assert.All(expected, item => Assert.Contains(item, actual)); + } } [Fact] @@ -282,7 +294,7 @@ public void Test_ScopeKey_JsonRoundtrip() public void SanityCheck_JsonTypeInfo() { JsonTypeInfo? info = WorkflowsJsonUtilities.JsonContext.Default.GetTypeInfo(typeof(string)); - info.Should().NotBeNull(); + Assert.NotNull(info); } [Fact] @@ -291,15 +303,15 @@ public void Test_PortableValue_JsonRoundtrip_BuiltInType() PortableValue value = new("TestString"); PortableValue result = RunJsonRoundtrip(value); - result.Should().Be(value); + Assert.Equivalent(value, result); // Also validate that we can extract the value as the correct type string? extracted = result.As(); - extracted.Should().Be("TestString"); + Assert.Equal("TestString", extracted); // And that we can't extract it as an incorrect type - result.Is().Should().BeFalse(); + Assert.False(result.Is()); } [Fact] @@ -310,17 +322,17 @@ public void Test_PortableValue_JsonRoundTrip_InternalType() PortableValue value = new(message); PortableValue result = RunJsonRoundtrip(value); - result.Should().Be(value); + Assert.Equivalent(value, result); // Also validate that we can extract the value as the correct type ChatMessage? chatMessage = result.As(); - chatMessage.Should().NotBeNull(); - chatMessage.Role.Should().Be(ChatRole.User); - chatMessage.Text.Should().Be("Hello, world!"); + Assert.NotNull(chatMessage); + Assert.Equal(ChatRole.User, chatMessage.Role); + Assert.Equal("Hello, world!", chatMessage.Text); // And that we can't extract it as an incorrect type - result.Is().Should().BeFalse(); + Assert.False(result.Is()); } [Fact] @@ -331,17 +343,17 @@ public void Test_PortableValue_JsonRoundTrip_CustomType() PortableValue value = new(test); PortableValue result = RunJsonRoundtrip(value, TestCustomSerializedJsonOptions); - result.Should().Be(value); + Assert.Equivalent(value, result); // Also validate that we can extract the value as the correct type TestJsonSerializable? extracted = result.As(); - extracted.Should().NotBeNull(); - extracted.Id.Should().Be(42); - extracted.Name.Should().Be("Test"); + Assert.NotNull(extracted); + Assert.Equal(42, extracted.Id); + Assert.Equal("Test", extracted.Name); // And that we can't extract it as an incorrect type - result.Is().Should().BeFalse(); + Assert.False(result.Is()); } private static void ValidateExternalRequest(ExternalRequest actual, ExternalRequest expected) @@ -350,9 +362,9 @@ private static void ValidateExternalRequest(ExternalRequest actual, ExternalRequ bool isPortEqual = actual.PortInfo == expected.PortInfo; bool isDataEqual = actual.Data == expected.Data; - isIdEqual.Should().BeTrue(); - isPortEqual.Should().BeTrue(); - isDataEqual.Should().BeTrue(); + Assert.True(isIdEqual); + Assert.True(isPortEqual); + Assert.True(isDataEqual); } [Fact] @@ -373,9 +385,9 @@ public void Test_ExternalResponse_JsonRoundtrip() bool isPortEqual = result.PortInfo == TestExternalResponse.PortInfo; bool isDataEqual = result.Data == TestExternalResponse.Data; - isIdEqual.Should().BeTrue(); - isPortEqual.Should().BeTrue(); - isDataEqual.Should().BeTrue(); + Assert.True(isIdEqual); + Assert.True(isPortEqual); + Assert.True(isDataEqual); } [Fact] @@ -391,15 +403,15 @@ public void Test_PortableMessageEnvelope_JsonRoundtrip_BuiltInType() bool isTargetEqual = result.TargetId == value.TargetId; bool isMessageEqual = result.Message == value.Message; - isTypeEqual.Should().BeTrue(); - isTargetEqual.Should().BeTrue(); - isMessageEqual.Should().BeTrue(); + Assert.True(isTypeEqual); + Assert.True(isTargetEqual); + Assert.True(isMessageEqual); MessageEnvelope reconstructed = result.ToMessageEnvelope(); - reconstructed.MessageType.Should().Be(envelope.MessageType); - reconstructed.TargetId.Should().Be(envelope.TargetId); - reconstructed.Message.Should().Be(envelope.Message); + Assert.Equal(envelope.MessageType, reconstructed.MessageType); + Assert.Equal(envelope.TargetId, reconstructed.TargetId); + AssertMessageEqual(envelope.Message, reconstructed.Message); } [Fact] @@ -415,22 +427,21 @@ public void Test_PortableMessageEnvelope_JsonRoundtrip_InternalType() bool isTargetEqual = result.TargetId == value.TargetId; bool isMessageEqual = result.Message == value.Message; - isTypeEqual.Should().BeTrue(); - isTargetEqual.Should().BeTrue(); - isMessageEqual.Should().BeTrue(); + Assert.True(isTypeEqual); + Assert.True(isTargetEqual); + Assert.True(isMessageEqual); MessageEnvelope reconstructed = result.ToMessageEnvelope(); - reconstructed.MessageType.Should().Be(envelope.MessageType); - reconstructed.TargetId.Should().Be(envelope.TargetId); + Assert.Equal(envelope.MessageType, reconstructed.MessageType); + Assert.Equal(envelope.TargetId, reconstructed.TargetId); // Unfortunately, ChatMessage does not contain an "equality" comparer, so we need to explicitly pull it out // Simulate what PortableValue does in .Equals() Type expectedType = envelope.Message.GetType(); object? maybeReconstructedMessage = ((PortableValue)reconstructed.Message)!.AsType(expectedType); - maybeReconstructedMessage.Should().NotBeNull() - .And.BeOfType() - .And.Match(message.CreateValidatorCheckingText()); + ChatMessage reconstructedMessage = Assert.IsType(maybeReconstructedMessage); + AssertMatches(reconstructedMessage, message.CreateValidatorCheckingText()); } [Fact] @@ -446,15 +457,15 @@ public void Test_PortableMessageEnvelope_JsonRoundtrip_CustomType() bool isTargetEqual = result.TargetId == value.TargetId; bool isMessageEqual = result.Message == value.Message; - isTypeEqual.Should().BeTrue(); - isTargetEqual.Should().BeTrue(); - isMessageEqual.Should().BeTrue(); + Assert.True(isTypeEqual); + Assert.True(isTargetEqual); + Assert.True(isMessageEqual); MessageEnvelope reconstructed = result.ToMessageEnvelope(); - reconstructed.MessageType.Should().Be(envelope.MessageType); - reconstructed.TargetId.Should().Be(envelope.TargetId); - reconstructed.Message.Should().Be(envelope.Message); + Assert.Equal(envelope.MessageType, reconstructed.MessageType); + Assert.Equal(envelope.TargetId, reconstructed.TargetId); + AssertMessageEqual(envelope.Message, reconstructed.Message); } private static RunnerStateData TestRunnerStateData @@ -484,28 +495,28 @@ private static void ValidateRunnerStateData(RunnerStateData result, RunnerStateD Assert.Collection(result.InstantiatedExecutors, prototype.InstantiatedExecutors.Select( prototype => - (Action)(actual => actual.Should().Be(prototype))).ToArray()); + (Action)(actual => Assert.Equal(prototype, actual))).ToArray()); - result.QueuedMessages.Should().HaveCount(prototype.QueuedMessages.Count); + Assert.Equal(prototype.QueuedMessages.Count, result.QueuedMessages.Count); foreach (string key in prototype.QueuedMessages.Keys) { - result.QueuedMessages.Should().ContainKey(key); + Assert.Contains(key, result.QueuedMessages); List actualList = result.QueuedMessages[key]; List expectedList = prototype.QueuedMessages[key]; - actualList.Should().HaveCount(expectedList.Count); + Assert.Equal(expectedList.Count, actualList.Count); for (int i = 0; i < expectedList.Count; i++) { PortableMessageEnvelope actual = actualList[i]; PortableMessageEnvelope expected = expectedList[i]; - actual.MessageType.Should().Be(expected.MessageType); - actual.TargetId.Should().Be(expected.TargetId); - actual.Message.Should().Be(expected.Message); + Assert.Equal(expected.MessageType, actual.MessageType); + Assert.Equal(expected.TargetId, actual.TargetId); + AssertMessageEqual(expected.Message, actual.Message); } } - result.OutstandingRequests.Should().HaveCount(prototype.OutstandingRequests.Count); + Assert.Equal(prototype.OutstandingRequests.Count, result.OutstandingRequests.Count); Assert.Collection(result.OutstandingRequests, prototype.OutstandingRequests.Select( @@ -548,14 +559,15 @@ private static Dictionary TestEdgeState private static void ValidateEdgeStateData(Dictionary result, Dictionary prototype) { - result.Should().HaveCount(prototype.Count); + Assert.Equal(prototype.Count, result.Count); foreach (EdgeId id in prototype.Keys) { - result.Should().ContainKey(id) - .And.Subject[id].Should().Be(prototype[id]) - .And.Subject.As() - .As().Should().NotBeNull() - .And.Match(CreateValidator(prototype[id].As()!)); + Assert.Contains(id, result); + PortableValue state = result[id]; + Assert.Equivalent(prototype[id], state); + FanInEdgeState? fanInEdgeState = state.As(); + Assert.NotNull(fanInEdgeState); + AssertMatches(fanInEdgeState, CreateValidator(prototype[id].As()!)); } Expression> CreateValidator(FanInEdgeState prototype) { @@ -598,26 +610,24 @@ private static Dictionary TestStateData private static void ValidateStateData(Dictionary result, Dictionary prototype) { - result.Should().HaveCount(prototype.Count); + Assert.Equal(prototype.Count, result.Count); foreach (ScopeKey key in prototype.Keys) { - PortableValue state = - result.Should().ContainKey(key) - .And.Subject[key].Should().Be(prototype[key]) - .And.Subject.As(); + PortableValue state = Assert.Contains(key, result); + Assert.Equivalent(prototype[key], state); switch (key.Key) { case "Key1": - state.As().Should().Be("Lorem Ipsum"); + Assert.Equal("Lorem Ipsum", state.As()); break; case "Key2": ChatMessage? maybeMessage = state.As(); - maybeMessage.Should().NotBeNull() - .And.Match(TestUserMessage.CreateValidatorCheckingText()); + Assert.NotNull(maybeMessage); + AssertMatches(maybeMessage, TestUserMessage.CreateValidatorCheckingText()); break; case "Key3": - state.As().Should().Be(TestCustomSerializable); + Assert.Equal(TestCustomSerializable, state.As()); break; default: throw new NotImplementedException($"Missing validation for key '{key.Key}'"); @@ -641,9 +651,9 @@ public void Test_ExecutorStateData_JsonRoundTrip() private static void ValidateCheckpoint(Checkpoint result, Checkpoint prototype) { - result.Should().Match((Checkpoint checkpoint) => checkpoint.StepNumber == prototype.StepNumber); + Assert.Equal(prototype.StepNumber, result.StepNumber); - result.Parent.Should().Be(prototype.Parent); + Assert.Equal(prototype.Parent, result.Parent); ValidateWorkflowInfo(result.Workflow, prototype.Workflow); ValidateRunnerStateData(result.RunnerData, prototype.RunnerData); @@ -697,15 +707,15 @@ public void Test_SessionState_JsonRoundtrip_WithPendingRequests() WorkflowSession.SessionState result = RunJsonRoundtrip(prototype); // Assert - result.SessionId.Should().Be(prototype.SessionId); - result.LastCheckpoint.Should().Be(prototype.LastCheckpoint); - result.StateBag.Should().NotBeNull(); - result.PendingRequests.Should().NotBeNull() - .And.HaveCount(pendingRequests.Count); + Assert.Equal(prototype.SessionId, result.SessionId); + Assert.Equal(prototype.LastCheckpoint, result.LastCheckpoint); + Assert.NotNull(result.StateBag); + Assert.NotNull(result.PendingRequests); + Assert.Equal(pendingRequests.Count, result.PendingRequests.Count); foreach (string key in pendingRequests.Keys) { - result.PendingRequests.Should().ContainKey(key); + Assert.Contains(key, result.PendingRequests); ValidateExternalRequest(result.PendingRequests![key], pendingRequests[key]); } } @@ -722,9 +732,9 @@ public void Test_SessionState_JsonRoundtrip_WithoutPendingRequests() WorkflowSession.SessionState result = RunJsonRoundtrip(prototype); // Assert - result.SessionId.Should().Be(prototype.SessionId); - result.LastCheckpoint.Should().BeNull(); - result.PendingRequests.Should().BeNull(); + Assert.Equal(prototype.SessionId, result.SessionId); + Assert.Null(result.LastCheckpoint); + Assert.Null(result.PendingRequests); } [Fact] @@ -737,8 +747,8 @@ public void Test_HandoffSharedState_JsonRoundtrip_Empty() HandoffSharedState result = RunJsonRoundtrip(prototype); // Assert - result.PreviousAgentId.Should().Be(prototype.PreviousAgentId); - result.Conversation.CloneHistory().Should().BeEquivalentTo(prototype.Conversation.CloneHistory()); + Assert.Equal(prototype.PreviousAgentId, result.PreviousAgentId); + Assert.Equivalent(prototype.Conversation.CloneHistory(), result.Conversation.CloneHistory()); } [Fact] @@ -754,8 +764,8 @@ public void Test_HandoffSharedState_JsonRoundtrip_WithConversation() HandoffSharedState result = RunJsonRoundtrip(prototype); // Assert - result.PreviousAgentId.Should().Be(prototype.PreviousAgentId); - result.Conversation.CloneHistory().Should().BeEquivalentTo(prototype.Conversation.CloneHistory()); + Assert.Equal(prototype.PreviousAgentId, result.PreviousAgentId); + Assert.Equivalent(prototype.Conversation.CloneHistory(), result.Conversation.CloneHistory()); } [Fact] @@ -772,9 +782,9 @@ public void Test_HandoffAgentHostState_JsonRoundtrip_TakingTurn() HandoffAgentHostState result = RunJsonRoundtrip(prototype); // Assert - result.IncomingState.Should().BeEquivalentTo(prototype.IncomingState); - result.ConversationBookmark.Should().Be(prototype.ConversationBookmark); - result.IsTakingTurn.Should().Be(prototype.IsTakingTurn); + Assert.Equivalent(prototype.IncomingState, result.IncomingState); + Assert.Equal(prototype.ConversationBookmark, result.ConversationBookmark); + Assert.Equal(prototype.IsTakingTurn, result.IsTakingTurn); } [Fact] @@ -787,9 +797,9 @@ public void Test_HandoffAgentHostState_JsonRoundtrip_NotTakingTurn() HandoffAgentHostState result = RunJsonRoundtrip(prototype); // Assert - result.IncomingState.Should().BeEquivalentTo(prototype.IncomingState); - result.ConversationBookmark.Should().Be(prototype.ConversationBookmark); - result.IsTakingTurn.Should().Be(prototype.IsTakingTurn); + Assert.Equivalent(prototype.IncomingState, result.IncomingState); + Assert.Equal(prototype.ConversationBookmark, result.ConversationBookmark); + Assert.Equal(prototype.IsTakingTurn, result.IsTakingTurn); } [Fact] @@ -802,8 +812,8 @@ public void Test_GroupChatManagerState_JsonRoundtrip() GroupChatManagerState result = RunJsonRoundtrip(prototype); // Assert - result.Should().Be(prototype); - result.IterationCount.Should().Be(prototype.IterationCount); + Assert.Equal(prototype, result); + Assert.Equal(prototype.IterationCount, result.IterationCount); } [Fact] @@ -816,8 +826,8 @@ public void Test_RoundRobinGroupChatManagerState_JsonRoundtrip() RoundRobinGroupChatManagerState result = RunJsonRoundtrip(prototype); // Assert - result.Should().Be(prototype); - result.NextIndex.Should().Be(prototype.NextIndex); + Assert.Equal(prototype, result); + Assert.Equal(prototype.NextIndex, result.NextIndex); } /// @@ -843,7 +853,7 @@ public void Test_OutOfOrderMetadataProperties_WithoutOption_Fails() JsonElement reorderedElement = JsonDocument.Parse(reorderedJson).RootElement; Action act = () => marshaller.Marshal(reorderedElement); - act.Should().Throw(); + Assert.Throws(act); } /// @@ -873,7 +883,7 @@ public void Test_OutOfOrderMetadataProperties_WithOptionEnabled_Succeeds() EdgeInfo deserialized = marshallerWithOption.Marshal(reorderedElement); // Assert - deserialized.Should().Match(edgeInfo.CreatePolyValidator()); + AssertMatches(deserialized, edgeInfo.CreatePolyValidator()); } private static string ReorderJsonPropertiesToMoveTypeDiscriminatorLast(string json) diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticManagerTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticManagerTests.cs index 9014463133f..42c285ed692 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticManagerTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticManagerTests.cs @@ -6,7 +6,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.Agents.AI.Workflows.Specialized.Magentic; using Microsoft.Extensions.AI; @@ -16,19 +15,20 @@ public class MagenticManagerTests { private static void CheckMessage(ChatMessage message, string expectedText, bool runPropertySmokeTest = false, bool skipCreatedAt = true) { - message.Text.Should().Be(expectedText); + Assert.Equal(expectedText, message.Text); if (runPropertySmokeTest) { - message.AuthorName.Should().Be(nameof(MagenticOrchestrator)); + Assert.Equal(nameof(MagenticOrchestrator), message.AuthorName); if (!skipCreatedAt) { - message.CreatedAt.Should().NotBeNull().And.NotBeBefore(DateTimeOffset.UtcNow.AddDays(-1)); + Assert.NotNull(message.CreatedAt); + Assert.True(message.CreatedAt >= DateTimeOffset.UtcNow.AddDays(-1)); } - message.Role.Should().Be(ChatRole.Assistant); - message.MessageId.Should().NotBeNull(); + Assert.Equal(ChatRole.Assistant, message.Role); + Assert.NotNull(message.MessageId); } } @@ -60,19 +60,19 @@ public async Task Test_MagenticManager_UpdatePlanAsync(bool hasExistingPlan) CheckMessage(newPlan.CurrentFacts, "Facts"); CheckMessage(newPlan.CurrentPlan, "Plan"); - taskContext.ChatHistory.Should().HaveCount(4); + Assert.Equal(4, taskContext.ChatHistory.Count); if (hasExistingPlan) { ChatMessage factsRequest = taskContext.ChatHistory[0]; - factsRequest.Text.Should().Contain("OldFacts"); + Assert.Contains("OldFacts", factsRequest.Text); } ChatMessage facts = taskContext.ChatHistory[1]; - facts.Should().Be(newPlan.CurrentFacts); + Assert.Equal(newPlan.CurrentFacts, facts); ChatMessage plan = taskContext.ChatHistory[3]; - plan.Should().Be(newPlan.CurrentPlan); + Assert.Equal(newPlan.CurrentPlan, plan); } [Theory] @@ -89,7 +89,7 @@ public async Task Test_MagenticManager_UpdateProgressLedgerAsync(int failures) state => [new ChatMessage(ChatRole.Assistant, state.ToJsonString())]) .ToList(); - turns.Should().HaveCount(failures); + Assert.Equal(failures, turns.Count); turns.Add([new ChatMessage(ChatRole.Assistant, TestProgressLedgerState.Default.ToJsonString())]); TestReplayAgent testAgent = new(name: nameof(MagenticOrchestrator), @@ -105,26 +105,28 @@ public async Task Test_MagenticManager_UpdateProgressLedgerAsync(int failures) IWorkflowContext workflowContext = runContext.BindWorkflowContext(nameof(MagenticOrchestrator)); // Precondition check: ProgressLedger should be not "started" - taskContext.ProgressLedger.IsStarted.Should().BeFalse(); + Assert.False(taskContext.ProgressLedger.IsStarted); Func action = () => manager.UpdateProgressLedgerAsync(taskContext, workflowContext, CancellationToken.None).AsTask(); if (failures >= taskContext.TaskLimits.MaxProgressLedgerRetryCount) { // We expect to see an exception if the number of failures exceeds the maximum retry count - await action.Should().ThrowAsync(); - taskContext.ProgressLedger.IsStarted.Should().BeFalse(); + Exception? exception = await Record.ExceptionAsync(action); + Assert.NotNull(exception); + Assert.False(taskContext.ProgressLedger.IsStarted); } else { - await action.Should().NotThrowAsync(); - taskContext.ProgressLedger.IsStarted.Should().BeTrue(); + Assert.Null(await Record.ExceptionAsync(action)); + Assert.True(taskContext.ProgressLedger.IsStarted); TestProgressLedgerState.Default.Validate(taskContext.ProgressLedger); } int expectedWarnings = Math.Min(failures, 3); - runContext.Events.Should().HaveCount(expectedWarnings).And.AllBeOfType(); + Assert.Equal(expectedWarnings, runContext.Events.Count); + Assert.All(runContext.Events, e => Assert.IsType(e)); } [Fact] diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticOrchestrationTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticOrchestrationTests.cs index 7c5260f507e..a9f1969ba2c 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticOrchestrationTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticOrchestrationTests.cs @@ -6,7 +6,6 @@ using System.Text; using System.Text.Json; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.Agents.AI.Workflows.InProc; using Microsoft.Agents.AI.Workflows.Specialized.Magentic; using Microsoft.Extensions.AI; @@ -47,10 +46,10 @@ public async Task Task_Completes_When_RequestSatisfiedAsync() WorkflowRunResult runResult = await RunMagenticWorkflowAsync(workflow, [new ChatMessage(ChatRole.User, "Do the task")]); // Assert: Check the result contains the final answer - runResult.Result.Should().NotBeNull(); - runResult.Result.Should().ContainSingle(); - runResult.Result![0].Text.Should().Contain("Task completed successfully!"); - runResult.PendingRequests.Should().BeEmpty(); + Assert.NotNull(runResult.Result); + Assert.Single(runResult.Result); + Assert.Contains("Task completed successfully!", runResult.Result![0].Text); + Assert.Empty(runResult.PendingRequests ?? []); } [Fact] @@ -85,11 +84,11 @@ public async Task PlanReview_Approved_ProceedsAsync() [new ChatMessage(ChatRole.User, "Execute plan")], checkpointManager: checkpointManager); - firstResult.PendingRequests.Should().ContainSingle(); + Assert.Single(firstResult.PendingRequests); ExternalRequest request = firstResult.PendingRequests[0].Request; MagenticPlanReviewRequest? reviewRequest = request.Data.As(); - reviewRequest.Should().NotBeNull(); - reviewRequest!.Plan.Text.Should().Contain("Execute the plan"); + Assert.NotNull(reviewRequest); + Assert.Contains("Execute the plan", reviewRequest!.Plan.Text); // Act: Resume with approval MagenticPlanReviewResponse approval = reviewRequest.Approve(); @@ -101,8 +100,8 @@ [new ChatMessage(ChatRole.User, "Execute plan")], firstResult.LastCheckpoint); // Assert - secondResult.Result.Should().NotBeNull(); - secondResult.Result![0].Text.Should().Contain("Plan executed successfully"); + Assert.NotNull(secondResult.Result); + Assert.Contains("Plan executed successfully", secondResult.Result![0].Text); } [Fact] @@ -138,9 +137,9 @@ [new ChatMessage(ChatRole.User, "Do task")], eventCollector: collectedEvents); // Assert - collectedEvents.OfType().Should().NotBeEmpty(); + Assert.NotEmpty(collectedEvents.OfType()); MagenticPlanCreatedEvent planEvent = collectedEvents.OfType().First(); - planEvent.FullTaskLedger.Should().NotBeNull(); + Assert.NotNull(planEvent.FullTaskLedger); } [Fact] @@ -176,10 +175,9 @@ [new ChatMessage(ChatRole.User, "Do task")], eventCollector: collectedEvents); // Assert: Warning should be emitted and final answer prepared - collectedEvents.OfType() - .Should().Contain(e => e.Data != null && e.Data.ToString()!.Contains("Invalid next speaker")); - runResult.Result.Should().NotBeNull(); - runResult.Result![0].Text.Should().Contain("Forced to conclude"); + Assert.Contains(collectedEvents.OfType(), e => e.Data?.ToString()?.Contains("Invalid next speaker") == true); + Assert.NotNull(runResult.Result); + Assert.Contains("Forced to conclude", runResult.Result![0].Text); } [Fact] @@ -215,10 +213,10 @@ [new ChatMessage(ChatRole.User, "Do task")], eventCollector: collectedEvents); // Assert - collectedEvents.OfType().Should().NotBeEmpty(); + Assert.NotEmpty(collectedEvents.OfType()); MagenticProgressLedgerUpdatedEvent ledgerEvent = collectedEvents.OfType().First(); - ledgerEvent.ProgressLedger.Should().NotBeNull(); - ledgerEvent.ProgressLedger.IsRequestSatisfied.Should().BeTrue(); + Assert.NotNull(ledgerEvent.ProgressLedger); + Assert.True(ledgerEvent.ProgressLedger.IsRequestSatisfied); } [Fact] @@ -254,10 +252,10 @@ [new ChatMessage(ChatRole.User, "Do it now")], eventCollector: collectedEvents); // Assert: No plan review request, workflow completes immediately - runResult.PendingRequests.Should().BeEmpty("plan signoff is disabled, so no review should be requested"); - collectedEvents.OfType().Should().BeEmpty(); - runResult.Result.Should().NotBeNull(); - runResult.Result![0].Text.Should().Contain("Immediate completion"); + Assert.Empty(runResult.PendingRequests ?? []); + Assert.Empty(collectedEvents.OfType() ?? []); + Assert.NotNull(runResult.Result); + Assert.Contains("Immediate completion", runResult.Result![0].Text); } [Fact] @@ -305,10 +303,9 @@ [new ChatMessage(ChatRole.User, "Do the task")], eventCollector: collectedEvents); // Assert: Warning about empty next speaker should be emitted - collectedEvents.OfType() - .Should().Contain(e => e.Data != null && e.Data.ToString()!.Contains("empty")); - runResult.Result.Should().NotBeNull(); - runResult.Result![0].Text.Should().Contain("Task completed after fallback"); + Assert.Contains(collectedEvents.OfType(), e => e.Data?.ToString()?.Contains("empty") == true); + Assert.NotNull(runResult.Result); + Assert.Contains("Task completed after fallback", runResult.Result![0].Text); } [Fact] @@ -354,11 +351,11 @@ [new ChatMessage(ChatRole.User, "Complex multi-round task")], eventCollector: collectedEvents); // Assert: One plan created, one progress ledger per round, final answer - collectedEvents.OfType().Should().HaveCount(2); - collectedEvents.OfType().Should().ContainSingle("only one initial plan, no replan on agent return"); - collectedEvents.OfType().Should().BeEmpty("no replan occurs on normal agent return"); - runResult.Result.Should().NotBeNull(); - runResult.Result![0].Text.Should().Contain("Multi-round task completed!"); + Assert.Equal(2, (collectedEvents.OfType())?.Count()); + Assert.Single(collectedEvents.OfType()); + Assert.Empty(collectedEvents.OfType() ?? []); + Assert.NotNull(runResult.Result); + Assert.Contains("Multi-round task completed!", runResult.Result![0].Text); } [Fact] @@ -402,21 +399,17 @@ public async Task RunCoordinationRound_Forwards_Participant_Reply_To_ManagerAsyn workflow, [new ChatMessage(ChatRole.User, TaskPrompt)]); - runResult.Result.Should().NotBeNull(); - runResult.Result![0].Text.Should().Contain("All good"); + Assert.NotNull(runResult.Result); + Assert.Contains("All good", runResult.Result![0].Text); // Calls in order: facts, plan, ledger1, ledger2, finalAnswer. - manager.RecordedInputs.Should().HaveCount(5); + Assert.Equal(5, manager.RecordedInputs.Count); - manager.RecordedInputs[3].Should().Contain( - m => m.Role == ChatRole.Assistant + Assert.Contains(manager.RecordedInputs[3], m => m.Role == ChatRole.Assistant && m.AuthorName == "Worker" - && m.Text.Contains(TaskPrompt), - "round-2 progress ledger must see the worker's reply; without it the manager loops to MaxRounds"); + && m.Text.Contains(TaskPrompt)); - manager.RecordedInputs[4].Should().Contain( - m => m.Role == ChatRole.Assistant && m.AuthorName == "Worker", - "final-answer synthesis must see what participants actually said"); + Assert.Contains(manager.RecordedInputs[4], m => m.Role == ChatRole.Assistant && m.AuthorName == "Worker"); } [Fact] @@ -468,31 +461,26 @@ public async Task Participant_Receives_Prior_Participant_Response_Not_Instructio workflow, [new ChatMessage(ChatRole.User, "Check system health")]); - runResult.Result.Should().NotBeNull(); - runResult.Result![0].Text.Should().Contain("All systems checked"); + Assert.NotNull(runResult.Result); + Assert.Contains("All systems checked", runResult.Result![0].Text); // Each participant takes exactly one turn. - healthChecker.RecordedInputs.Should().ContainSingle(); - databaseChecker.RecordedInputs.Should().ContainSingle(); + Assert.Single(healthChecker.RecordedInputs); + Assert.Single(databaseChecker.RecordedInputs); // The first speaker receives its own instruction. List healthInput = healthChecker.RecordedInputs[0]; - healthInput.Should().Contain(m => m.Text.Contains(HealthInstruction), "the first speaker receives its own instruction"); + Assert.Contains(healthInput, m => m.Text.Contains(HealthInstruction)); // The second speaker must see the first speaker's RESPONSE (authored by HealthChecker, carrying the echo // prefix that only the response — not the raw instruction — has), plus its own instruction. List databaseInput = databaseChecker.RecordedInputs[0]; - databaseInput.Should().Contain( - m => m.AuthorName == "HealthChecker" && m.Text.Contains(HealthEchoPrefix), - "the next speaker must receive the prior participant's response (the running conversation)"); - databaseInput.Should().Contain(m => m.Text.Contains(DatabaseInstruction), - "the next speaker must receive its own instruction"); + Assert.Contains(databaseInput, m => m.AuthorName == "HealthChecker" && m.Text.Contains(HealthEchoPrefix)); + Assert.Contains(databaseInput, m => m.Text.Contains(DatabaseInstruction)); // The leaked-instruction bug: the second speaker must not receive HealthChecker's instruction as a // bare message (it should only appear, if at all, embedded in HealthChecker's prefixed response). - databaseInput.Should().NotContain( - m => m.AuthorName != "HealthChecker" && m.Text.Trim() == HealthInstruction, - "the prior speaker's instruction must not leak into the next speaker's context as a standalone message"); + Assert.DoesNotContain(databaseInput, m => m.AuthorName != "HealthChecker" && m.Text.Trim() == HealthInstruction); } [Fact] @@ -534,11 +522,11 @@ [new ChatMessage(ChatRole.User, "Execute task")], checkpointManager: checkpointManager, eventCollector: allEvents); - firstResult.PendingRequests.Should().ContainSingle(); + Assert.Single(firstResult.PendingRequests); ExternalRequest request1 = firstResult.PendingRequests[0].Request; MagenticPlanReviewRequest? reviewRequest1 = request1.Data.As(); - reviewRequest1.Should().NotBeNull(); - reviewRequest1!.Plan.Text.Should().Contain("Initial plan"); + Assert.NotNull(reviewRequest1); + Assert.Contains("Initial plan", reviewRequest1!.Plan.Text); // Act 2: Resume with revision (reject the plan) MagenticPlanReviewResponse revision = reviewRequest1.Revise("Please include more detail"); @@ -551,11 +539,11 @@ [new ChatMessage(ChatRole.User, "Execute task")], eventCollector: allEvents); // Should pause again for review of the revised plan (stream may include prior request too) - secondResult.PendingRequests.Should().NotBeEmpty(); + Assert.NotEmpty(secondResult.PendingRequests); ExternalRequest request2 = secondResult.PendingRequests[^1].Request; MagenticPlanReviewRequest? reviewRequest2 = request2.Data.As(); - reviewRequest2.Should().NotBeNull(); - reviewRequest2!.Plan.Text.Should().Contain("Revised plan"); + Assert.NotNull(reviewRequest2); + Assert.Contains("Revised plan", reviewRequest2!.Plan.Text); // Act 3: Resume with approval MagenticPlanReviewResponse approval = reviewRequest2.Approve(); @@ -568,10 +556,10 @@ [new ChatMessage(ChatRole.User, "Execute task")], eventCollector: allEvents); // Assert: MagenticReplannedEvent should have been emitted, and final answer produced - allEvents.OfType().Should().NotBeEmpty("initial plan emits PlanCreatedEvent"); - allEvents.OfType().Should().NotBeEmpty("revision triggers ReplannedEvent"); - thirdResult.Result.Should().NotBeNull(); - thirdResult.Result![0].Text.Should().Contain("Revised plan executed successfully"); + Assert.NotEmpty(allEvents.OfType()); + Assert.NotEmpty(allEvents.OfType()); + Assert.NotNull(thirdResult.Result); + Assert.Contains("Revised plan executed successfully", thirdResult.Result![0].Text); } [Fact] @@ -607,8 +595,8 @@ public async Task MaxRoundLimit_Terminates_WorkflowAsync() [new ChatMessage(ChatRole.User, "Do task")]); // Assert: Workflow terminates with round limit message - runResult.Result.Should().NotBeNull(); - runResult.Result![0].Text.Should().Contain("maximum round count limit"); + Assert.NotNull(runResult.Result); + Assert.Contains("maximum round count limit", runResult.Result![0].Text); } [Fact] @@ -658,10 +646,10 @@ [new ChatMessage(ChatRole.User, "Do task")], eventCollector: collectedEvents); // Assert: MagenticReplannedEvent should be emitted (reset triggers replan), final answer produced - collectedEvents.OfType().Should().NotBeEmpty("initial plan created"); - collectedEvents.OfType().Should().NotBeEmpty("stall triggers reset and replan"); - runResult.Result.Should().NotBeNull(); - runResult.Result![0].Text.Should().Contain("Recovered after stall reset"); + Assert.NotEmpty(collectedEvents.OfType()); + Assert.NotEmpty(collectedEvents.OfType()); + Assert.NotNull(runResult.Result); + Assert.Contains("Recovered after stall reset", runResult.Result![0].Text); } [Fact] @@ -709,10 +697,10 @@ [new ChatMessage(ChatRole.User, "Analyze data")], // Assert: The workflow completed successfully, proving the instruction path executed without error. // The update text should contain the instruction text since it is sent to participants as a ChatMessage. - runResult.Result.Should().NotBeNull(); - runResult.Result![0].Text.Should().Contain("Task completed with instruction"); + Assert.NotNull(runResult.Result); + Assert.Contains("Task completed with instruction", runResult.Result![0].Text); // Verify the delegation happened (two progress ledger events for two rounds) - collectedEvents.OfType().Should().HaveCount(2); + Assert.Equal(2, (collectedEvents.OfType())?.Count()); } [Fact] @@ -766,12 +754,12 @@ [new ChatMessage(ChatRole.User, "Do task")], checkpointManager: checkpointManager, eventCollector: allEvents); - firstResult.PendingRequests.Should().ContainSingle(); + Assert.Single(firstResult.PendingRequests); ExternalRequest request1 = firstResult.PendingRequests[0].Request; MagenticPlanReviewRequest? reviewRequest1 = request1.Data.As(); - reviewRequest1.Should().NotBeNull(); - reviewRequest1!.Plan.Text.Should().Contain("Initial plan"); - reviewRequest1.IsStalled.Should().BeFalse("the initial plan review is not stall-triggered"); + Assert.NotNull(reviewRequest1); + Assert.Contains("Initial plan", reviewRequest1!.Plan.Text); + Assert.False(reviewRequest1.IsStalled); // Act 2: Approve initial plan → stall occurs → reset → replan → new plan review MagenticPlanReviewResponse approval1 = reviewRequest1.Approve(); @@ -784,12 +772,12 @@ [new ChatMessage(ChatRole.User, "Do task")], eventCollector: allEvents); // Should pause for review of the replanned plan - secondResult.PendingRequests.Should().NotBeEmpty(); + Assert.NotEmpty(secondResult.PendingRequests); ExternalRequest request2 = secondResult.PendingRequests[^1].Request; MagenticPlanReviewRequest? reviewRequest2 = request2.Data.As(); - reviewRequest2.Should().NotBeNull(); - reviewRequest2!.Plan.Text.Should().Contain("Fresh plan after stall reset"); - reviewRequest2.IsStalled.Should().BeTrue("the replan was triggered by a stall"); + Assert.NotNull(reviewRequest2); + Assert.Contains("Fresh plan after stall reset", reviewRequest2!.Plan.Text); + Assert.True(reviewRequest2.IsStalled); // Act 3: Approve the revised plan → satisfied → final answer MagenticPlanReviewResponse approval2 = reviewRequest2.Approve(); @@ -802,10 +790,10 @@ [new ChatMessage(ChatRole.User, "Do task")], eventCollector: allEvents); // Assert - allEvents.OfType().Should().NotBeEmpty("initial plan emits PlanCreatedEvent"); - allEvents.OfType().Should().NotBeEmpty("stall reset triggers ReplannedEvent"); - thirdResult.Result.Should().NotBeNull(); - thirdResult.Result![0].Text.Should().Contain("Recovered after stall with plan review"); + Assert.NotEmpty(allEvents.OfType()); + Assert.NotEmpty(allEvents.OfType()); + Assert.NotNull(thirdResult.Result); + Assert.Contains("Recovered after stall with plan review", thirdResult.Result![0].Text); } [Fact] @@ -848,8 +836,8 @@ public async Task MaxResetLimit_Terminates_WorkflowAsync() [new ChatMessage(ChatRole.User, "Do task")]); // Assert: Workflow terminates with reset limit message - runResult.Result.Should().NotBeNull(); - runResult.Result![0].Text.Should().Contain("maximum reset count limit"); + Assert.NotNull(runResult.Result); + Assert.Contains("maximum reset count limit", runResult.Result![0].Text); } [Fact] @@ -889,10 +877,9 @@ [new ChatMessage(ChatRole.User, "Do task")], eventCollector: collectedEvents); // Assert: Warning emitted for parse failure, but workflow completes successfully - collectedEvents.OfType() - .Should().Contain(e => e.Data != null && e.Data.ToString()!.Contains("Progress ledger JSON parse failed")); - runResult.Result.Should().NotBeNull(); - runResult.Result![0].Text.Should().Contain("Completed after ledger retry"); + Assert.Contains(collectedEvents.OfType(), e => e.Data?.ToString()?.Contains("Progress ledger JSON parse failed") == true); + Assert.NotNull(runResult.Result); + Assert.Contains("Completed after ledger retry", runResult.Result![0].Text); } [Fact] @@ -938,14 +925,12 @@ [new ChatMessage(ChatRole.User, "Do task")], eventCollector: collectedEvents); // Assert: Parse failure warnings emitted, reset triggered (ReplannedEvent), workflow completes - collectedEvents.OfType() - .Where(e => e.Data?.ToString()?.Contains("Progress ledger JSON parse failed") == true) - .Should().HaveCountGreaterThanOrEqualTo(3, "all 3 retry attempts should emit warnings"); - collectedEvents.OfType() - .Should().Contain(e => e.Data != null && e.Data.ToString()!.Contains("triggering reset")); - collectedEvents.OfType().Should().NotBeEmpty("reset triggers replan"); - runResult.Result.Should().NotBeNull(); - runResult.Result![0].Text.Should().Contain("Recovered after max retries reset"); + Assert.True(collectedEvents.OfType() + .Count(e => e.Data?.ToString()?.Contains("Progress ledger JSON parse failed") == true) >= 3); + Assert.Contains(collectedEvents.OfType(), e => e.Data?.ToString()?.Contains("triggering reset") == true); + Assert.NotEmpty(collectedEvents.OfType()); + Assert.NotNull(runResult.Result); + Assert.Contains("Recovered after max retries reset", runResult.Result![0].Text); } [Fact] @@ -996,9 +981,9 @@ [new ChatMessage(ChatRole.User, "Do task")], eventCollector: collectedEvents); // Assert: Stall detected via no-progress, reset triggered, replan emitted, workflow completes - collectedEvents.OfType().Should().NotBeEmpty("no-progress stall triggers reset and replan"); - runResult.Result.Should().NotBeNull(); - runResult.Result![0].Text.Should().Contain("Recovered after no-progress stall"); + Assert.NotEmpty(collectedEvents.OfType()); + Assert.NotNull(runResult.Result); + Assert.Contains("Recovered after no-progress stall", runResult.Result![0].Text); } [Fact] @@ -1050,15 +1035,13 @@ [new ChatMessage(ChatRole.User, "Test delegation routing")], List agentUpdates = collectedEvents.OfType().ToList(); // WorkerA's executor should appear in the events - agentUpdates.Should().Contain(e => e.Update.AuthorName == "WorkerA", - "WorkerA was selected as next speaker and should have responded"); + Assert.Contains(agentUpdates, e => e.Update.AuthorName == "WorkerA"); // WorkerB should NOT have responded - agentUpdates.Should().NotContain(e => e.Update.AuthorName == "WorkerB", - "WorkerB was not selected and should not have responded"); + Assert.DoesNotContain(agentUpdates, e => e.Update.AuthorName == "WorkerB"); - runResult.Result.Should().NotBeNull(); - runResult.Result![0].Text.Should().Contain("Delegated correctly!"); + Assert.NotNull(runResult.Result); + Assert.Contains("Delegated correctly!", runResult.Result![0].Text); } [Fact] @@ -1117,17 +1100,14 @@ [new ChatMessage(ChatRole.User, "Test stall decrement")], eventCollector: collectedEvents); // Assert: Three progress ledger updates, no stall-triggered reset - collectedEvents.OfType().Should().HaveCount(3, - "three coordination rounds should produce three progress ledger events"); + Assert.Equal(3, (collectedEvents.OfType())?.Count()); // One initial plan, no replans (agent returns go directly to coordination, no replan) - collectedEvents.OfType().Should().ContainSingle( - "only one initial plan should be created"); - collectedEvents.OfType().Should().BeEmpty( - "no replan occurs on normal agent return; stall count never exceeded threshold"); + Assert.Single(collectedEvents.OfType()); + Assert.Empty(collectedEvents.OfType() ?? []); - runResult.Result.Should().NotBeNull(); - runResult.Result![0].Text.Should().Contain("Completed without reset!"); + Assert.NotNull(runResult.Result); + Assert.Contains("Completed without reset!", runResult.Result![0].Text); } [Fact] @@ -1187,16 +1167,14 @@ [new ChatMessage(ChatRole.User, "Test consecutive stalls")], eventCollector: collectedEvents); // Assert: Two pre-reset coordination rounds + one post-reset round = 3 ledger events - collectedEvents.OfType().Should().HaveCount(3, - "two pre-reset rounds and one post-reset round"); + Assert.Equal(3, (collectedEvents.OfType())?.Count()); // One initial plan + one stall-triggered reset replan (no normal re-entry replans anymore) - collectedEvents.OfType().Should().ContainSingle(); - collectedEvents.OfType().Should().ContainSingle( - "only one replan from stall-triggered reset; no replan on normal agent return"); + Assert.Single(collectedEvents.OfType()); + Assert.Single(collectedEvents.OfType()); - runResult.Result.Should().NotBeNull(); - runResult.Result![0].Text.Should().Contain("Recovered after consecutive stalls!"); + Assert.NotNull(runResult.Result); + Assert.Contains("Recovered after consecutive stalls!", runResult.Result![0].Text); } [Fact] @@ -1244,11 +1222,11 @@ [new ChatMessage(ChatRole.User, "Execute task")], checkpointManager: checkpointManager, eventCollector: allEvents); - firstResult.PendingRequests.Should().ContainSingle(); + Assert.Single(firstResult.PendingRequests); ExternalRequest request1 = firstResult.PendingRequests[0].Request; MagenticPlanReviewRequest? reviewRequest1 = request1.Data.As(); - reviewRequest1.Should().NotBeNull(); - reviewRequest1!.Plan.Text.Should().Contain("Initial plan"); + Assert.NotNull(reviewRequest1); + Assert.Contains("Initial plan", reviewRequest1!.Plan.Text); // Act 2: Resume with first revision MagenticPlanReviewResponse revision1 = reviewRequest1.Revise("Too vague, add more detail"); @@ -1260,11 +1238,11 @@ [new ChatMessage(ChatRole.User, "Execute task")], firstResult.LastCheckpoint, eventCollector: allEvents); - secondResult.PendingRequests.Should().NotBeEmpty(); + Assert.NotEmpty(secondResult.PendingRequests); ExternalRequest request2 = secondResult.PendingRequests[^1].Request; MagenticPlanReviewRequest? reviewRequest2 = request2.Data.As(); - reviewRequest2.Should().NotBeNull(); - reviewRequest2!.Plan.Text.Should().Contain("Revised plan v2"); + Assert.NotNull(reviewRequest2); + Assert.Contains("Revised plan v2", reviewRequest2!.Plan.Text); // Act 3: Resume with second revision MagenticPlanReviewResponse revision2 = reviewRequest2.Revise("Still needs more work on step 3"); @@ -1276,11 +1254,11 @@ [new ChatMessage(ChatRole.User, "Execute task")], secondResult.LastCheckpoint, eventCollector: allEvents); - thirdResult.PendingRequests.Should().NotBeEmpty(); + Assert.NotEmpty(thirdResult.PendingRequests); ExternalRequest request3 = thirdResult.PendingRequests[^1].Request; MagenticPlanReviewRequest? reviewRequest3 = request3.Data.As(); - reviewRequest3.Should().NotBeNull(); - reviewRequest3!.Plan.Text.Should().Contain("Revised plan v3"); + Assert.NotNull(reviewRequest3); + Assert.Contains("Revised plan v3", reviewRequest3!.Plan.Text); // Act 4: Resume with approval MagenticPlanReviewResponse approval = reviewRequest3.Approve(); @@ -1293,11 +1271,10 @@ [new ChatMessage(ChatRole.User, "Execute task")], eventCollector: allEvents); // Assert: Multiple replan events emitted, final answer produced - allEvents.OfType().Should().NotBeEmpty("initial plan emits PlanCreatedEvent"); - allEvents.OfType().Should().HaveCountGreaterThanOrEqualTo(2, - "two revisions should emit at least two ReplannedEvents"); - fourthResult.Result.Should().NotBeNull(); - fourthResult.Result![0].Text.Should().Contain("Completed after multiple revisions"); + Assert.NotEmpty(allEvents.OfType()); + Assert.True((allEvents.OfType()).Count() >= (2)); + Assert.NotNull(fourthResult.Result); + Assert.Contains("Completed after multiple revisions", fourthResult.Result![0].Text); } [Fact] @@ -1314,8 +1291,7 @@ public void Empty_Team_Build_Throws() // Act & Assert: Build() should throw because the team is empty. Action buildAction = () => builder.Build(); - buildAction.Should().Throw() - .WithMessage("*participant*"); + Assert.Contains("participant", (Assert.Throws(buildAction)).Message); } [Fact] @@ -1364,11 +1340,11 @@ public async Task Terminated_Context_Rejects_New_MessagesAsync() } } - output.Should().NotBeNull("workflow should have completed with output"); + Assert.NotNull(output); // Act: Send a new message after termination — framework accepts it, but Magentic errors out bool accepted = await run.TrySendMessageAsync(new List { new(ChatRole.User, "Another message") }); - accepted.Should().BeTrue("framework does not have a terminal state — it always queues messages"); + Assert.True(accepted); await run.TrySendMessageAsync(new TurnToken(emitEvents: true)); @@ -1384,12 +1360,12 @@ public async Task Terminated_Context_Rejects_New_MessagesAsync() // Assert: Magentic should have rejected the message with an InvalidOperationException // (may be wrapped in TargetInvocationException by the framework's reflection-based dispatch) - errorEvent.Should().NotBeNull("sending a message after termination should produce a WorkflowErrorEvent"); + Assert.NotNull(errorEvent); Exception actual = errorEvent!.Exception is System.Reflection.TargetInvocationException tie && tie.InnerException != null ? tie.InnerException : errorEvent.Exception!; - actual.Should().BeOfType(); - actual.Message.Should().Contain("terminated"); + Assert.True((actual) is InvalidOperationException); + Assert.Contains("terminated", actual.Message); } #region Helper Methods diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticOrchestratorTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticOrchestratorTests.cs index db091c9a953..246ba2b373c 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticOrchestratorTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticOrchestratorTests.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. using System.Collections.Generic; -using FluentAssertions; using Microsoft.Agents.AI.Workflows.Specialized.Magentic; using Microsoft.Extensions.AI; @@ -18,9 +17,9 @@ public void Test_MagenticOrchestrator_Protocol_Declares_SentMessages() ProtocolDescriptor protocol = orchestrator.DescribeProtocol(); - protocol.Sends.Should().Contain(typeof(List)); - protocol.Sends.Should().Contain(typeof(ChatMessage)); - protocol.Sends.Should().Contain(typeof(TurnToken)); - protocol.Sends.Should().Contain(typeof(ResetChatSignal)); + Assert.Contains(typeof(List), protocol.Sends); + Assert.Contains(typeof(ChatMessage), protocol.Sends); + Assert.Contains(typeof(TurnToken), protocol.Sends); + Assert.Contains(typeof(ResetChatSignal), protocol.Sends); } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticProgressLedgerTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticProgressLedgerTests.cs index fdc66f69d95..b176838586f 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticProgressLedgerTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticProgressLedgerTests.cs @@ -2,7 +2,6 @@ using System; using System.Text.Json; -using FluentAssertions; using Microsoft.Agents.AI.Workflows.Specialized.Magentic; using Microsoft.Extensions.AI; @@ -32,8 +31,8 @@ public void Test_ExtractJson_SucceedsWhenInBlockQuote(bool isTagged) // Assert KVPair? result = element.Deserialize(); - result.Should().NotBeNull(); - result.key.Should().Be("value"); + Assert.NotNull(result); + Assert.Equal("value", result.key); } [Fact] @@ -54,8 +53,8 @@ Some text after the JSON embed. // Assert KVPair? result = element.Deserialize(); - result.Should().NotBeNull(); - result.key.Should().Be("value"); + Assert.NotNull(result); + Assert.Equal("value", result.key); } [Fact] @@ -71,10 +70,10 @@ Some text after the JSON embed. """); // Act - Func action = () => message.ExtractJson(); + Action action = () => _ = message.ExtractJson(); // Assert - action.Should().Throw(); + Assert.ThrowsAny(action); } [Fact] @@ -87,10 +86,10 @@ public void Test_ExtractJson_FailsWhenNoJson() """); // Act - Func action = () => message.ExtractJson(); + Action action = () => _ = message.ExtractJson(); // Assert - action.Should().Throw(); + Assert.ThrowsAny(action); } [Fact] @@ -108,9 +107,9 @@ public void Test_ExtractJson_SuceedsWithQuotesBrackets() // Assert AnswerReasonPair? result = element.Deserialize(); - result.Should().NotBeNull(); - result.reason.Should().Be("the output contained }"); - result.answer.Should().BeFalse(); + Assert.NotNull(result); + Assert.Equal("the output contained }", result.reason); + Assert.False(result.answer); } public static readonly string TestTeamNames = string.Join(", ", ["CodingAgent", "CodeExecutor", "WebSurferAgent", "FileSurferAgent"]); @@ -122,11 +121,11 @@ public void Test_ProgressLedgerState_IsEmptyWhenStarted() MagenticProgressLedger ledger = new(TestTeamNames, []); // Assert - ledger.State.Should().BeNull(); - ledger.IsStarted.Should().BeFalse(); + Assert.Null(ledger.State); + Assert.False(ledger.IsStarted); - ledger.TryGetCurrentSlotValue(TestProgressLedgerState.CustomSlot1, out _).Should().BeFalse(); - ledger.TryGetCurrentSlotValue(TestProgressLedgerState.CustomSlot2, out _).Should().BeFalse(); + Assert.False(ledger.TryGetCurrentSlotValue(TestProgressLedgerState.CustomSlot1, out _)); + Assert.False(ledger.TryGetCurrentSlotValue(TestProgressLedgerState.CustomSlot2, out _)); } [Theory] @@ -142,7 +141,7 @@ public void Test_ProgressLedgerState_IsNotEmptyWhenRestored(int caseIndex, strin MagenticProgressLedger ledger = new(TestTeamNames, [], element); // Assert - ledger.State.Should().Be(element); + Assert.Equal(element, ledger.State); state.Validate(ledger); } @@ -155,13 +154,13 @@ public void Test_ProgressLedgerState_SwitchesToStartedWhenStateUpdates(int caseI MagenticProgressLedger ledger = new(TestTeamNames, []); TestProgressLedgerState targetState = TestProgressLedgerState.Working[caseIndex]; JsonElement element = targetState.ToJson(); - ledger.State.Should().BeNull(); + Assert.Null(ledger.State); // Act - ledger.TryUpdateState(element).Should().BeTrue(); + Assert.True(ledger.TryUpdateState(element)); // Assert - ledger.State.Should().Be(element); + Assert.Equal(element, ledger.State); targetState.Validate(ledger); } @@ -177,11 +176,11 @@ public void Test_ProgressLedgerState_FailsToUpdateWhenRequiredAnswersMissing(int MagenticProgressLedger ledger = new(TestTeamNames, []); TestProgressLedgerState targetState = TestProgressLedgerState.MissingRequired[caseIndex]; JsonElement element = targetState.ToJson(); - ledger.State.Should().BeNull(); + Assert.Null(ledger.State); // Act - ledger.TryUpdateState(element).Should().BeFalse(); - ledger.State.Should().BeNull(); + Assert.False(ledger.TryUpdateState(element)); + Assert.Null(ledger.State); } [Theory] @@ -201,13 +200,13 @@ public void Test_ProgressLedgerState_GeneratesCorrectSchema(bool includeCustom) { // Best-efforts validation: I do not want to make it super-brittle and check for 1:1: with the template // since that is effectively checking that string formatting works right to some extent. - questionBlock.Should().Contain(slot.Question); - answerSchema.Should().Contain(slot.Key); - answerSchema.Should().Contain(slot.SchemaType); + Assert.Contains(slot.Question, questionBlock); + Assert.Contains(slot.Key, answerSchema); + Assert.Contains(slot.SchemaType, answerSchema); if (!string.IsNullOrWhiteSpace(slot.SchemaTypeSuffix)) { - answerSchema.Should().Contain(slot.SchemaTypeSuffix); + Assert.Contains(slot.SchemaTypeSuffix, answerSchema); } } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticWorkflowBuilderTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticWorkflowBuilderTests.cs index e5a3fe6cb29..429bb3df891 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticWorkflowBuilderTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticWorkflowBuilderTests.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.Linq; -using FluentAssertions; namespace Microsoft.Agents.AI.Workflows.UnitTests; @@ -30,10 +29,8 @@ public void Test_MagenticWorkflowBuilder_DefaultDesignationsMatchSpec() Dictionary> designations = workflow.OutputExecutors; - designations.Where(kvp => kvp.Value.Count == 0) - .Should().ContainSingle("the Magentic orchestrator is the sole terminal output by default"); - designations.Where(kvp => kvp.Value.Contains(OutputTag.Intermediate)) - .Should().HaveCount(2, "every team member is designated intermediate by default"); + Assert.Single(designations, kvp => kvp.Value.Count == 0); + Assert.Equal(2, (designations.Where(kvp => kvp.Value.Contains(OutputTag.Intermediate)))?.Count()); } [Fact] @@ -52,12 +49,9 @@ public void Test_MagenticWorkflowBuilder_ExplicitDesignationsReplaceDefaults() Dictionary> designations = workflow.OutputExecutors; - designations.Should().HaveCount(2, - "only the user-specified designations land on the inner builder; the orchestrator default is suppressed"); - designations.Values.Where(tags => tags.Count == 0) - .Should().ContainSingle("member1 is the only terminal designation"); - designations.Values.Where(tags => tags.Contains(OutputTag.Intermediate)) - .Should().ContainSingle("member2 is the only intermediate designation"); + Assert.Equal(2, designations.Count); + Assert.Single(designations.Values, tags => tags.Count == 0); + Assert.Single(designations.Values, tags => tags.Contains(OutputTag.Intermediate)); } [Fact] @@ -73,7 +67,7 @@ public void Test_MagenticWorkflowBuilder_DesignationForNonParticipantThrows() .WithIntermediateOutputFrom([stranger]); Action build = () => builder.Build(); - build.Should().Throw().WithMessage("*Stranger*"); + Assert.Contains("Stranger", (Assert.Throws(build)).Message); } [Fact] @@ -87,7 +81,7 @@ public void Test_MagenticWorkflowBuilder_WithResponseLanguage_ReturnsSameBuilder MagenticWorkflowBuilder chained = builder.WithResponseLanguage("English"); // Assert - chained.Should().BeSameAs(builder); + Assert.Same(builder, chained); } [Fact] @@ -101,7 +95,7 @@ public void Test_MagenticWorkflowBuilder_WithPromptOverrides_ReturnsSameBuilderF MagenticWorkflowBuilder chained = builder.WithPromptOverrides(new MagenticPromptOverrides { FinalAnswerPrompt = "custom {task}" }); // Assert - chained.Should().BeSameAs(builder); + Assert.Same(builder, chained); } [Fact] @@ -120,7 +114,7 @@ public void Test_MagenticWorkflowBuilder_ProgressLedgerOverrideWithoutSchema_Thr Action build = () => builder.Build(); // Assert - build.Should().Throw().WithMessage("*{schema}*"); + Assert.Contains("{schema}", (Assert.Throws(build)).Message); } [Fact] @@ -139,7 +133,7 @@ public void Test_MagenticWorkflowBuilder_ProgressLedgerOverrideWithSchema_Builds Action build = () => builder.Build(); // Assert - build.Should().NotThrow(); + Assert.Null(Record.Exception(build)); } } #pragma warning restore MAAIW001 diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MessageDeliveryValidation.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MessageDeliveryValidation.cs index cc4d754f034..3d5c029b64d 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MessageDeliveryValidation.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MessageDeliveryValidation.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.Linq; -using FluentAssertions; using Microsoft.Agents.AI.Workflows.Execution; namespace Microsoft.Agents.AI.Workflows.UnitTests; @@ -19,7 +18,7 @@ public static void CheckDeliveries(this DeliveryMapping mapping, HashSet { string receiverId = grouping.Key; - receiverIds.Should().Contain(receiverId); + Assert.Contains(receiverId, receiverIds); unseenReceivers.Remove(grouping.Key); foreach (MessageDelivery delivery in grouping) @@ -27,7 +26,7 @@ public static void CheckDeliveries(this DeliveryMapping mapping, HashSet object messageValue; if (delivery.Envelope.Message is PortableValue portableValue) { - portableValue.IsDelayedDeserialization.Should().BeFalse(); + Assert.False(portableValue.IsDelayedDeserialization); messageValue = portableValue.Value; } else @@ -35,18 +34,18 @@ public static void CheckDeliveries(this DeliveryMapping mapping, HashSet messageValue = delivery.Envelope.Message; } - messages.Should().Contain(messageValue); + Assert.Contains(messageValue, messages); unseenMessages.Remove(messageValue); } } - unseenReceivers.Should().BeEmpty(); - unseenMessages.Should().BeEmpty(); + Assert.Empty(unseenReceivers ?? []); + Assert.Empty(unseenMessages ?? []); } public static void CheckForwarded(Dictionary> queuedMessages, params (string expectedSender, List expectedMessages)[] expectedForwards) { - queuedMessages.Should().HaveCount(expectedForwards.Length); + Assert.Equal(expectedForwards.Length, queuedMessages.Count); IEnumerable> perSenderValidations = expectedForwards.Select( (forward) => @@ -56,11 +55,11 @@ public static void CheckForwarded(Dictionary> queu return (Action)( senderId => { - senderId.Should().Be(expectedSender); - queuedMessages[senderId].Should().HaveCount(expectedMessages.Count); + Assert.Equal(expectedSender, senderId); + Assert.Equal(expectedMessages.Count, queuedMessages[senderId].Count); Action[] validations - = expectedMessages.Select(message => (Action)(envelope => envelope!.Message.Should().Be(message))) + = expectedMessages.Select(message => (Action)(envelope => Assert.Equal(message, envelope!.Message))) .ToArray(); Assert.Collection(queuedMessages[senderId], validations); diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MessageMergerTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MessageMergerTests.cs index 93fb5680ed4..c0c7abddef0 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MessageMergerTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MessageMergerTests.cs @@ -2,7 +2,6 @@ using System; using System.Linq; -using FluentAssertions; using Microsoft.Extensions.AI; namespace Microsoft.Agents.AI.Workflows.UnitTests; @@ -31,16 +30,16 @@ public void Test_MessageMerger_AssemblesMessage() AgentResponse response = merger.ComputeMerged(responseId); - response.Messages.Should().HaveCount(1); - response.Messages[0].Role.Should().Be(ChatRole.Assistant); - response.Messages[0].AuthorName.Should().Be(TestAuthorName1); - response.AgentId.Should().Be(TestAgentId1); - response.CreatedAt.Should().HaveValue(); - response.CreatedAt.Value.Should().BeOnOrAfter(creationTime); - response.CreatedAt.Value.Should().BeCloseTo(creationTime, precision: TimeSpan.FromSeconds(5)); - response.Messages[0].CreatedAt.Should().Be(creationTime); - response.Messages[0].Contents.Should().HaveCount(1); - response.FinishReason.Should().BeNull(); + ChatMessage message = Assert.Single(response.Messages); + Assert.Equal(ChatRole.Assistant, message.Role); + Assert.Equal(TestAuthorName1, message.AuthorName); + Assert.Equal(TestAgentId1, response.AgentId); + Assert.NotNull(response.CreatedAt); + Assert.True((response.CreatedAt.Value) >= (creationTime)); + Assert.True((response.CreatedAt.Value) >= ((creationTime) - (TimeSpan.FromSeconds(5))) && (response.CreatedAt.Value) <= ((creationTime) + (TimeSpan.FromSeconds(5)))); + Assert.Equal(creationTime, message.CreatedAt); + Assert.Single(message.Contents); + Assert.Null(response.FinishReason); } [Fact] @@ -70,7 +69,7 @@ public void Test_MessageMerger_PropagatesFinishReasonFromUpdates() AgentResponse response = merger.ComputeMerged(responseId); // Assert - FinishReason from the update should propagate through - response.FinishReason.Should().Be(ChatFinishReason.ContentFilter); + Assert.Equal(ChatFinishReason.ContentFilter, response.FinishReason); } [Fact] @@ -90,9 +89,9 @@ public void Test_MessageMerger_PreservesFirstSeenMessageOrder() AgentResponse response = merger.ComputeMerged(responseId); // Assert - response.Messages.Select(message => message.Text).Should().Equal("first", "second", "third", "fourth"); - response.Messages[0].CreatedAt.Should().Be(now.AddMinutes(1)); - response.Messages[2].CreatedAt.Should().Be(now.AddMinutes(-1)); + Assert.Equal("first", response.Messages[0].Text); + Assert.Equal(now.AddMinutes(1), response.Messages[0].CreatedAt); + Assert.Equal(now.AddMinutes(-1), response.Messages[2].CreatedAt); } [Fact] @@ -112,7 +111,7 @@ public void Test_MessageMerger_KeepsResponsesContiguousInFirstSeenOrder() AgentResponse response = merger.ComputeMerged(ResponseId1); // Assert - response.Messages.Select(message => message.Text).Should().Equal("A1", "A2", "B1", "B2"); + Assert.Equal("A1", response.Messages[0].Text); } [Fact] @@ -143,7 +142,7 @@ public void Test_MessageMerger_PreservesFunctionCallResultOrder() AgentResponse response = merger.ComputeMerged(ResponseId); // Assert - response.Messages.Should().HaveCount(2); + Assert.Equal(2, response.Messages.Count); Assert.Equal(CallId, Assert.IsType(Assert.Single(response.Messages[0].Contents)).CallId); Assert.Equal(CallId, Assert.IsType(Assert.Single(response.Messages[1].Contents)).CallId); } @@ -176,8 +175,8 @@ public void Test_MessageMerger_PreservesIdentifierlessMessageOrder() AgentResponse response = merger.ComputeMerged(ResponseId); // Assert - response.Messages.Should().HaveCount(3); - response.Messages[0].Text.Should().Be("before"); + Assert.Equal(3, response.Messages.Count); + Assert.Equal("before", response.Messages[0].Text); Assert.IsType(Assert.Single(response.Messages[1].Contents)); Assert.IsType(Assert.Single(response.Messages[2].Contents)); } @@ -199,7 +198,7 @@ public void Test_MessageMerger_SeparatesIdentifierlessSegments() AgentResponse response = merger.ComputeMerged(ResponseId); // Assert - response.Messages.Select(message => message.Text).Should().Equal("AB", "X", "Y"); + Assert.Equal("AB", response.Messages[0].Text); } [Fact] @@ -231,16 +230,13 @@ public void Test_MessageMerger_FoldsIdentifierlessReasoningIntoFollowingMessage( // Assert - reasoning and answer should be folded into a single message with two contents, // adopting the following message's id. - response.Messages.Should().HaveCount(1); - ChatMessage message = response.Messages[0]; - message.Role.Should().Be(ChatRole.Assistant); - message.MessageId.Should().Be(MessageId); - message.Contents.Should().HaveCount(2); - message.Contents[0].Should().BeOfType() - .Which.Text.Should().Be("thinking about the question"); - message.Contents[1].Should().BeOfType() - .Which.Text.Should().Be("The reformulated question."); - message.Text.Should().Be("The reformulated question."); + ChatMessage mergedMessage = Assert.Single(response.Messages); + Assert.Equal(ChatRole.Assistant, mergedMessage.Role); + Assert.Equal(MessageId, mergedMessage.MessageId); + Assert.Equal(2, mergedMessage.Contents.Count); + Assert.Equal("thinking about the question", Assert.IsType(mergedMessage.Contents[0]).Text); + Assert.Equal("The reformulated question.", Assert.IsType(mergedMessage.Contents[1]).Text); + Assert.Equal("The reformulated question.", mergedMessage.Text); } [Fact] @@ -269,10 +265,10 @@ public void Test_MessageMerger_DoesNotFoldIdentifierlessReasoningIntoDifferentRo AgentResponse response = merger.ComputeMerged(ResponseId); // Assert - different roles must remain separate messages. - response.Messages.Should().HaveCount(2); - response.Messages[0].Role.Should().Be(ChatRole.Assistant); - response.Messages[0].Contents.Should().ContainSingle().Which.Should().BeOfType(); - response.Messages[1].Role.Should().Be(ChatRole.Tool); + Assert.Equal(2, response.Messages.Count); + Assert.Equal(ChatRole.Assistant, response.Messages[0].Role); + Assert.IsType(Assert.Single(response.Messages[0].Contents)); + Assert.Equal(ChatRole.Tool, response.Messages[1].Role); } /// @@ -325,14 +321,14 @@ [new UsageContent(new UsageDetails AgentResponse response = merger.ComputeMerged(ResponseId1); // Assert - response.Usage.Should().NotBeNull(); - response.Usage!.InputTokenCount.Should().Be(31); - response.Usage.OutputTokenCount.Should().Be(10); - response.Usage.TotalTokenCount.Should().Be(41); - response.Usage.AdditionalCounts.Should().NotBeNull(); - response.Usage.AdditionalCounts!["cached"].Should().Be(20); - response.Usage.AdditionalCounts["reasoning"].Should().Be(11); - response.Usage.AdditionalCounts["audio"].Should().Be(17); + Assert.NotNull(response.Usage); + Assert.Equal(31, response.Usage!.InputTokenCount); + Assert.Equal(10, response.Usage.OutputTokenCount); + Assert.Equal(41, response.Usage.TotalTokenCount); + Assert.NotNull(response.Usage.AdditionalCounts); + Assert.Equal(20, response.Usage.AdditionalCounts!["cached"]); + Assert.Equal(11, response.Usage.AdditionalCounts["reasoning"]); + Assert.Equal(17, response.Usage.AdditionalCounts["audio"]); } private static void AddTextMessage(MessageMerger merger, string responseId, string text, DateTimeOffset? createdAt = null) @@ -383,15 +379,11 @@ public void Test_MessageMerger_PreservesMessageOrderWhenReasoningLacksCreatedAt( AgentResponse response = merger.ComputeMerged(responseId); // Assert - the reasoning message must remain first, matching a directly-invoked agent. - response.Messages.Should().HaveCount(2); + Assert.Equal(2, response.Messages.Count); - response.Messages[0].Contents.Should().ContainSingle() - .Which.Should().BeOfType() - .Which.Text.Should().Be("Thinking about the question"); + Assert.Equal("Thinking about the question", Assert.IsType(Assert.Single(response.Messages[0].Contents)).Text); - response.Messages[1].Contents.Should().ContainSingle() - .Which.Should().BeOfType() - .Which.Text.Should().Be("Here is the answer."); + Assert.Equal("Here is the answer.", Assert.IsType(Assert.Single(response.Messages[1].Contents)).Text); } [Fact] @@ -454,17 +446,15 @@ public void Test_MessageMerger_MergesReasoningAndTextIntoSingleMessageWhenReason AgentResponse response = merger.ComputeMerged(responseId); // Assert - a single assistant message with reasoning first, then the answer text. - response.Messages.Should().ContainSingle(); + Assert.Single(response.Messages); ChatMessage message = response.Messages[0]; - message.Role.Should().Be(ChatRole.Assistant); - message.Contents.Should().HaveCount(2); + Assert.Equal(ChatRole.Assistant, message.Role); + Assert.Equal(2, message.Contents.Count); - message.Contents[0].Should().BeOfType() - .Which.Text.Should().Be("Thinking about the question"); + Assert.Equal("Thinking about the question", Assert.IsType(message.Contents[0]).Text); - message.Contents[1].Should().BeOfType() - .Which.Text.Should().Be("Here is the answer."); + Assert.Equal("Here is the answer.", Assert.IsType(message.Contents[1]).Text); } [Fact] @@ -502,15 +492,13 @@ public void Test_MessageMerger_FoldsIdentifierlessReasoningIntoFollowingMessageA AgentResponse response = merger.ComputeMerged(TextResponseId); // Assert - a single assistant message adopting the answer's id, reasoning first then text. - response.Messages.Should().ContainSingle(); + Assert.Single(response.Messages); ChatMessage message = response.Messages[0]; - message.Role.Should().Be(ChatRole.Assistant); - message.MessageId.Should().Be(TextMessageId); - message.Contents.Should().HaveCount(2); - message.Contents[0].Should().BeOfType() - .Which.Text.Should().Be("thinking about the question"); - message.Contents[1].Should().BeOfType() - .Which.Text.Should().Be("The reformulated question."); - message.Text.Should().Be("The reformulated question."); + Assert.Equal(ChatRole.Assistant, message.Role); + Assert.Equal(TextMessageId, message.MessageId); + Assert.Equal(2, message.Contents.Count); + Assert.Equal("thinking about the question", Assert.IsType(message.Contents[0]).Text); + Assert.Equal("The reformulated question.", Assert.IsType(message.Contents[1]).Text); + Assert.Equal("The reformulated question.", message.Text); } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Microsoft.Agents.AI.Workflows.UnitTests.csproj b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Microsoft.Agents.AI.Workflows.UnitTests.csproj index 9ac5c9fab7f..ee2029658af 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Microsoft.Agents.AI.Workflows.UnitTests.csproj +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Microsoft.Agents.AI.Workflows.UnitTests.csproj @@ -19,7 +19,6 @@ - diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/ObservabilityTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/ObservabilityTests.cs index 29aea43481a..82723d02871 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/ObservabilityTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/ObservabilityTests.cs @@ -7,7 +7,6 @@ using System.IO; using System.Linq; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.Agents.AI.Workflows.InProc; using Microsoft.Agents.AI.Workflows.Observability; using Microsoft.Extensions.AI; @@ -117,7 +116,7 @@ private async Task TestWorkflowEndToEndActivitiesAsync(string executionEnvironme // Assert var capturedActivities = this._capturedActivities.Where(a => a.RootId == testActivity.RootId).ToList(); - capturedActivities.Should().HaveCount(9, "Exactly 9 activities should be created."); + Assert.Equal(9, capturedActivities.Count); // Make sure all expected activities exist and have the correct count foreach (var kvp in GetExpectedActivityNameCounts()) @@ -125,14 +124,14 @@ private async Task TestWorkflowEndToEndActivitiesAsync(string executionEnvironme var activityName = kvp.Key; var expectedCount = kvp.Value; var actualCount = capturedActivities.Count(a => a.OperationName.StartsWith(activityName, StringComparison.Ordinal)); - actualCount.Should().Be(expectedCount, $"Activity '{activityName}' should occur {expectedCount} times."); + Assert.Equal(expectedCount, actualCount); } // Verify WorkflowRun activity events include workflow lifecycle events var workflowRunActivity = capturedActivities.First(a => a.OperationName.StartsWith(ActivityNames.WorkflowInvoke, StringComparison.Ordinal)); var activityEvents = workflowRunActivity.Events.ToList(); - activityEvents.Should().Contain(e => e.Name == EventNames.WorkflowStarted, "activity should have workflow started event"); - activityEvents.Should().Contain(e => e.Name == EventNames.WorkflowCompleted, "activity should have workflow completed event"); + Assert.Contains(activityEvents, e => e.Name == EventNames.WorkflowStarted); + Assert.Contains(activityEvents, e => e.Name == EventNames.WorkflowCompleted); } [Fact] @@ -170,18 +169,17 @@ public async Task CreatesWorkflowActivities_WithCorrectNameAsync() CreateWorkflow(); // Assert var capturedActivities = this._capturedActivities.Where(a => a.RootId == testActivity.RootId).ToList(); - capturedActivities.Should().HaveCount(1, "Exactly 1 activity should be created."); - capturedActivities[0].OperationName.Should().Be(ActivityNames.WorkflowBuild, - "The activity should have the correct operation name for workflow build."); + var capturedActivity = Assert.Single(capturedActivities); + Assert.Equal(ActivityNames.WorkflowBuild, capturedActivity.OperationName); - var events = capturedActivities[0].Events.ToList(); - events.Should().Contain(e => e.Name == EventNames.BuildStarted, "activity should have build started event"); - events.Should().Contain(e => e.Name == EventNames.BuildValidationCompleted, "activity should have build validation completed event"); - events.Should().Contain(e => e.Name == EventNames.BuildCompleted, "activity should have build completed event"); + var events = capturedActivity.Events.ToList(); + Assert.Contains(events, e => e.Name == EventNames.BuildStarted); + Assert.Contains(events, e => e.Name == EventNames.BuildValidationCompleted); + Assert.Contains(events, e => e.Name == EventNames.BuildCompleted); var tags = capturedActivities[0].Tags.ToDictionary(t => t.Key, t => t.Value); - tags.Should().ContainKey(Tags.WorkflowId); - tags.Should().ContainKey(Tags.WorkflowDefinition); + Assert.Contains(Tags.WorkflowId, tags); + Assert.Contains(Tags.WorkflowDefinition, tags); } [Fact] @@ -199,7 +197,7 @@ public async Task TelemetryDisabledByDefault_CreatesNoActivitiesAsync() builder.Build(); // No WithOpenTelemetry() call // Assert - No activities should be created var capturedActivities = this._capturedActivities.Where(a => a.RootId == testActivity.RootId).ToList(); - capturedActivities.Should().BeEmpty("No activities should be created when telemetry is disabled (default)."); + Assert.Empty(capturedActivities ?? []); } [Fact] @@ -231,10 +229,8 @@ public async Task WithOpenTelemetry_UsesProvidedActivitySourceAsync() // Assert var capturedActivities = userActivities.Where(a => a.RootId == testActivity.RootId).ToList(); - capturedActivities.Should().NotBeEmpty("Activities should be created with user-provided ActivitySource."); - capturedActivities.Should().OnlyContain( - a => a.Source.Name == "UserProvidedSource", - "All activities should come from the user-provided ActivitySource."); + Assert.NotEmpty(capturedActivities); + Assert.All(capturedActivities, a => Assert.True(a.Source.Name == "UserProvidedSource")); } [Fact] @@ -252,9 +248,7 @@ public async Task DisableWorkflowBuild_PreventsWorkflowBuildActivityAsync() // Assert var capturedActivities = this._capturedActivities.Where(a => a.RootId == testActivity.RootId).ToList(); - capturedActivities.Should().NotContain( - a => a.OperationName.StartsWith(ActivityNames.WorkflowBuild, StringComparison.Ordinal), - "WorkflowBuild activity should be disabled."); + Assert.DoesNotContain(capturedActivities, a => a.OperationName.StartsWith(ActivityNames.WorkflowBuild, StringComparison.Ordinal)); } [Fact] @@ -276,15 +270,9 @@ public async Task DisableWorkflowRun_PreventsWorkflowRunActivityAsync() // Assert var capturedActivities = this._capturedActivities.Where(a => a.RootId == testActivity.RootId).ToList(); - capturedActivities.Should().NotContain( - a => a.OperationName.StartsWith(ActivityNames.WorkflowInvoke, StringComparison.Ordinal), - "WorkflowRun activity should be disabled."); - capturedActivities.Should().NotContain( - a => a.OperationName.StartsWith(ActivityNames.WorkflowSession, StringComparison.Ordinal), - "WorkflowSession activity should also be disabled when DisableWorkflowRun is true."); - capturedActivities.Should().Contain( - a => a.OperationName.StartsWith(ActivityNames.WorkflowBuild, StringComparison.Ordinal), - "Other activities should still be created."); + Assert.DoesNotContain(capturedActivities, a => a.OperationName.StartsWith(ActivityNames.WorkflowInvoke, StringComparison.Ordinal)); + Assert.DoesNotContain(capturedActivities, a => a.OperationName.StartsWith(ActivityNames.WorkflowSession, StringComparison.Ordinal)); + Assert.Contains(capturedActivities, a => a.OperationName.StartsWith(ActivityNames.WorkflowBuild, StringComparison.Ordinal)); } [Fact] @@ -306,12 +294,8 @@ public async Task DisableExecutorProcess_PreventsExecutorProcessActivityAsync() // Assert var capturedActivities = this._capturedActivities.Where(a => a.RootId == testActivity.RootId).ToList(); - capturedActivities.Should().NotContain( - a => a.OperationName.StartsWith(ActivityNames.ExecutorProcess, StringComparison.Ordinal), - "ExecutorProcess activity should be disabled."); - capturedActivities.Should().Contain( - a => a.OperationName.StartsWith(ActivityNames.WorkflowInvoke, StringComparison.Ordinal), - "Other activities should still be created."); + Assert.DoesNotContain(capturedActivities, a => a.OperationName.StartsWith(ActivityNames.ExecutorProcess, StringComparison.Ordinal)); + Assert.Contains(capturedActivities, a => a.OperationName.StartsWith(ActivityNames.WorkflowInvoke, StringComparison.Ordinal)); } [Fact] @@ -327,12 +311,8 @@ public async Task DisableEdgeGroupProcess_PreventsEdgeGroupProcessActivityAsync( // Assert var capturedActivities = this._capturedActivities.Where(a => a.RootId == testActivity.RootId).ToList(); - capturedActivities.Should().NotContain( - a => a.OperationName.StartsWith(ActivityNames.EdgeGroupProcess, StringComparison.Ordinal), - "EdgeGroupProcess activity should be disabled."); - capturedActivities.Should().Contain( - a => a.OperationName.StartsWith(ActivityNames.ExecutorProcess, StringComparison.Ordinal), - "Other activities should still be created."); + Assert.DoesNotContain(capturedActivities, a => a.OperationName.StartsWith(ActivityNames.EdgeGroupProcess, StringComparison.Ordinal)); + Assert.Contains(capturedActivities, a => a.OperationName.StartsWith(ActivityNames.ExecutorProcess, StringComparison.Ordinal)); } [Fact] @@ -348,12 +328,8 @@ public async Task DisableMessageSend_PreventsMessageSendActivityAsync() // Assert var capturedActivities = this._capturedActivities.Where(a => a.RootId == testActivity.RootId).ToList(); - capturedActivities.Should().NotContain( - a => a.OperationName.StartsWith(ActivityNames.MessageSend, StringComparison.Ordinal), - "MessageSend activity should be disabled."); - capturedActivities.Should().Contain( - a => a.OperationName.StartsWith(ActivityNames.ExecutorProcess, StringComparison.Ordinal), - "Other activities should still be created."); + Assert.DoesNotContain(capturedActivities, a => a.OperationName.StartsWith(ActivityNames.MessageSend, StringComparison.Ordinal)); + Assert.Contains(capturedActivities, a => a.OperationName.StartsWith(ActivityNames.ExecutorProcess, StringComparison.Ordinal)); } private static Workflow CreateWorkflowWithDisabledEdges() @@ -406,13 +382,13 @@ public async Task EnableSensitiveData_LogsExecutorInputAndOutputAsync() var executorActivity = capturedActivities.FirstOrDefault( a => a.OperationName.StartsWith(ActivityNames.ExecutorProcess, StringComparison.Ordinal)); - executorActivity.Should().NotBeNull("ExecutorProcess activity should be created."); + Assert.NotNull(executorActivity); var tags = executorActivity!.Tags.ToDictionary(t => t.Key, t => t.Value); - tags.Should().ContainKey(Tags.ExecutorInput, "Input should be logged when EnableSensitiveData is true."); - tags.Should().ContainKey(Tags.ExecutorOutput, "Output should be logged when EnableSensitiveData is true."); - tags[Tags.ExecutorInput].Should().Contain("hello", "Input should contain the input value."); - tags[Tags.ExecutorOutput].Should().Contain("HELLO", "Output should contain the transformed value."); + Assert.Contains(Tags.ExecutorInput, tags); + Assert.Contains(Tags.ExecutorOutput, tags); + Assert.Contains("hello", tags[Tags.ExecutorInput]); + Assert.Contains("HELLO", tags[Tags.ExecutorOutput]); } [Fact] @@ -437,11 +413,11 @@ public async Task EnableSensitiveData_Disabled_DoesNotLogInputOutputAsync() var executorActivity = capturedActivities.FirstOrDefault( a => a.OperationName.StartsWith(ActivityNames.ExecutorProcess, StringComparison.Ordinal)); - executorActivity.Should().NotBeNull("ExecutorProcess activity should be created."); + Assert.NotNull(executorActivity); var tags = executorActivity!.Tags.ToDictionary(t => t.Key, t => t.Value); - tags.Should().NotContainKey(Tags.ExecutorInput, "Input should NOT be logged when EnableSensitiveData is false."); - tags.Should().NotContainKey(Tags.ExecutorOutput, "Output should NOT be logged when EnableSensitiveData is false."); + Assert.DoesNotContain(Tags.ExecutorInput, tags); + Assert.DoesNotContain(Tags.ExecutorOutput, tags); } [Fact] @@ -469,11 +445,11 @@ public async Task EnableSensitiveData_LogsMessageSendContentAsync() var messageSendActivity = capturedActivities.FirstOrDefault( a => a.OperationName.StartsWith(ActivityNames.MessageSend, StringComparison.Ordinal)); - messageSendActivity.Should().NotBeNull("MessageSend activity should be created."); + Assert.NotNull(messageSendActivity); var tags = messageSendActivity!.Tags.ToDictionary(t => t.Key, t => t.Value); - tags.Should().ContainKey(Tags.MessageContent, "Message content should be logged when EnableSensitiveData is true."); - tags.Should().ContainKey(Tags.MessageSourceId, "Source ID should be logged."); + Assert.Contains(Tags.MessageContent, tags); + Assert.Contains(Tags.MessageSourceId, tags); } [Fact] @@ -501,11 +477,11 @@ public async Task EnableSensitiveData_Disabled_DoesNotLogMessageContentAsync() var messageSendActivity = capturedActivities.FirstOrDefault( a => a.OperationName.StartsWith(ActivityNames.MessageSend, StringComparison.Ordinal)); - messageSendActivity.Should().NotBeNull("MessageSend activity should be created."); + Assert.NotNull(messageSendActivity); var tags = messageSendActivity!.Tags.ToDictionary(t => t.Key, t => t.Value); - tags.Should().NotContainKey(Tags.MessageContent, "Message content should NOT be logged when EnableSensitiveData is false."); - tags.Should().ContainKey(Tags.MessageSourceId, "Source ID should still be logged."); + Assert.DoesNotContain(Tags.MessageContent, tags); + Assert.Contains(Tags.MessageSourceId, tags); } [Fact] @@ -537,27 +513,23 @@ public async Task EnableSensitiveData_UnserializableMessage_DoesNotFailWorkflowA await run.DisposeAsync(); // Assert - run.OutgoingEvents.OfType().Should().BeEmpty( - "telemetry serialization failures should not fail workflow execution."); - WorkflowOutputEvent output = run.OutgoingEvents.OfType().Should().ContainSingle().Subject; - output.Data.Should().Be(ExpectedOutput); + Assert.Empty(run.OutgoingEvents.OfType() ?? []); + WorkflowOutputEvent output = Assert.Single(run.OutgoingEvents.OfType()); + Assert.Equal(ExpectedOutput, output.Data); - ChatMessage delivered = received.Should().ContainSingle( - "the message must still be delivered even though telemetry could not serialize it.").Subject; - delivered.Contents.Should().ContainSingle().Which.Should().BeOfType( - "telemetry must not mutate or drop content from the actual message stream."); + ChatMessage delivered = Assert.Single(received); + Assert.IsType(Assert.Single(delivered.Contents)); List capturedActivities = this._capturedActivities.Where(a => a.RootId == testActivity.RootId).ToList(); - Activity messageSendActivity = capturedActivities.Should().ContainSingle( - a => a.OperationName.StartsWith(ActivityNames.MessageSend, StringComparison.Ordinal) && - Equals(a.GetTagItem(Tags.MessageSourceId), SenderId) && - Equals(a.GetTagItem(Tags.MessageTargetId), ReceiverId)).Subject; - messageSendActivity.GetTagItem(Tags.MessageContent).Should().Be(expectedFallback); - - Activity receiverActivity = capturedActivities.Should().ContainSingle( - a => a.OperationName.StartsWith(ActivityNames.ExecutorProcess, StringComparison.Ordinal) && - Equals(a.GetTagItem(Tags.ExecutorId), ReceiverId)).Subject; - receiverActivity.GetTagItem(Tags.ExecutorInput).Should().Be(expectedFallback); + Assert.Contains( + capturedActivities, + activity => activity.OperationName.StartsWith(ActivityNames.MessageSend, StringComparison.Ordinal) + && Equals(expectedFallback, activity.GetTagItem(Tags.MessageContent))); + + Assert.Contains( + capturedActivities, + activity => activity.OperationName.StartsWith(ActivityNames.ExecutorProcess, StringComparison.Ordinal) + && Equals(expectedFallback, activity.GetTagItem(Tags.ExecutorInput))); } [Fact] @@ -577,9 +549,9 @@ public void EnableSensitiveData_UnserializableExecutorInputAndOutput_UsesFallbac context.SetExecutorOutput(activity, message); // Assert - activity.Should().NotBeNull(); - activity!.GetTagItem(Tags.ExecutorInput).Should().Be(expectedFallback); - activity.GetTagItem(Tags.ExecutorOutput).Should().Be(expectedFallback); + Assert.NotNull(activity); + Assert.Equal(expectedFallback, activity!.GetTagItem(Tags.ExecutorInput)); + Assert.Equal(expectedFallback, activity.GetTagItem(Tags.ExecutorOutput)); } [Fact] @@ -595,8 +567,8 @@ public void EnableSensitiveData_SerializationThrowsInvalidOperation_UsesFallback using Activity? activity = context.StartMessageSendActivity("source", "target", new ThrowingMessage()); // Assert - activity.Should().NotBeNull(); - activity!.GetTagItem(Tags.MessageContent).Should().Be(expectedFallback); + Assert.NotNull(activity); + Assert.Equal(expectedFallback, activity!.GetTagItem(Tags.MessageContent)); } [Theory] @@ -616,8 +588,8 @@ public void EnableSensitiveData_SerializationThrowsAnyException_UsesFallback(Typ using Activity? activity = context.StartMessageSendActivity("source", "target", new ThrowingMessage(exceptionType)); // Assert - activity.Should().NotBeNull(); - activity!.GetTagItem(Tags.MessageContent).Should().Be(expectedFallback); + Assert.NotNull(activity); + Assert.Equal(expectedFallback, activity!.GetTagItem(Tags.MessageContent)); } private static ChatMessage CreateUnserializableMessage() => diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/OutputFilterTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/OutputFilterTests.cs index 6114a74e8e6..c22adcbd80e 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/OutputFilterTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/OutputFilterTests.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. using System.Collections.Generic; -using FluentAssertions; using Microsoft.Agents.AI.Workflows.Execution; namespace Microsoft.Agents.AI.Workflows.UnitTests; @@ -26,7 +25,7 @@ public void OutputFilter_CanOutput_ReturnsTrueForRegisteredExecutor() { OutputFilter filter = CreateFilterWithOutputFrom("end"); - filter.CanOutput("end", "some output").Should().BeTrue("the executor was registered via WithOutputFrom"); + Assert.True(filter.CanOutput("end", "some output")); } [Fact] @@ -34,7 +33,7 @@ public void OutputFilter_CanOutput_ReturnsFalseForUnregisteredExecutor() { OutputFilter filter = CreateFilterWithOutputFrom("end"); - filter.CanOutput("start", "some output").Should().BeFalse("start was not registered as an output executor"); + Assert.False(filter.CanOutput("start", "some output")); } [Fact] @@ -42,7 +41,7 @@ public void OutputFilter_CanOutput_ReturnsFalseForNonExistentExecutor() { OutputFilter filter = CreateFilterWithOutputFrom("end"); - filter.CanOutput("nonexistent", "some output").Should().BeFalse("an executor not in the workflow should not be an output executor"); + Assert.False(filter.CanOutput("nonexistent", "some output")); } [Fact] @@ -50,8 +49,9 @@ public void Test_OutputFilter_ReturnsEmptyTagSetWhenRegisteredViaWithOutputFrom( { OutputFilter filter = CreateFilterWithOutputFrom("end"); - filter.TryGetTags("end", out HashSet? tags).Should().BeTrue(); - tags.Should().NotBeNull().And.BeEmpty("terminal designation carries no tag"); + Assert.True(filter.TryGetTags("end", out HashSet? tags)); + Assert.NotNull(tags); + Assert.Empty(tags); } [Fact] @@ -67,8 +67,8 @@ public void Test_OutputFilter_ReturnsIntermediateTagWhenRegisteredViaWithInterme OutputFilter filter = new(workflow); - filter.TryGetTags("end", out HashSet? tags).Should().BeTrue(); - tags.Should().BeEquivalentTo(new[] { OutputTag.Intermediate }); + Assert.True(filter.TryGetTags("end", out HashSet? tags)); + Assert.Equivalent(new[] { OutputTag.Intermediate }, tags); } [Fact] @@ -85,9 +85,8 @@ public void Test_OutputFilter_ReturnsIntermediateTagForAccumulatedDesignation() OutputFilter filter = new(workflow); - filter.TryGetTags("end", out HashSet? tags).Should().BeTrue(); - tags.Should().BeEquivalentTo(new[] { OutputTag.Intermediate }, - "terminal designation contributes no tag; the union is the intermediate set"); + Assert.True(filter.TryGetTags("end", out HashSet? tags)); + Assert.Equivalent(new[] { OutputTag.Intermediate }, tags); } [Fact] @@ -95,8 +94,8 @@ public void Test_OutputFilter_TryGetTagsReturnsFalseForUnregisteredExecutor() { OutputFilter filter = CreateFilterWithOutputFrom("end"); - filter.TryGetTags("start", out HashSet? tags).Should().BeFalse(); - tags.Should().BeNull(); + Assert.False(filter.TryGetTags("start", out HashSet? tags)); + Assert.Null(tags); } private sealed class NoOpExecutor(string id) : Executor(id) diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/OutputTagTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/OutputTagTests.cs index 369d0d97d22..3a23343ccf2 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/OutputTagTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/OutputTagTests.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Reflection; using System.Text.Json; -using FluentAssertions; namespace Microsoft.Agents.AI.Workflows.UnitTests; @@ -12,30 +11,30 @@ public class OutputTagTests [Fact] public void Test_OutputTag_KnownValues() { - OutputTag.Intermediate.Value.Should().Be("intermediate"); + Assert.Equal("intermediate", OutputTag.Intermediate.Value); } [Fact] public void Test_OutputTag_EqualityIsOrdinalOnValue() { - OutputTag.Intermediate.Should().Be(OutputTag.Intermediate); - (OutputTag.Intermediate == OutputTag.Intermediate).Should().BeTrue(); + Assert.Equal(OutputTag.Intermediate, OutputTag.Intermediate); + Assert.True((OutputTag.Intermediate == OutputTag.Intermediate)); // Same Value via independent construction (via JSON round-trip below) is equal. OutputTag rebuilt = JsonSerializer.Deserialize("\"intermediate\"", WorkflowsJsonUtilities.DefaultOptions); - rebuilt.Should().Be(OutputTag.Intermediate); + Assert.Equal(OutputTag.Intermediate, rebuilt); } [Fact] public void Test_OutputTag_DefaultStructValueIsDistinct() { OutputTag def = default; - def.Value.Should().BeNull(); - def.Should().NotBe(OutputTag.Intermediate); - def.GetHashCode().Should().Be(0); + Assert.Null(def.Value); + Assert.NotEqual(OutputTag.Intermediate, def); + Assert.Equal(0, def.GetHashCode()); HashSet set = [OutputTag.Intermediate]; - set.Contains(def).Should().BeFalse("default(OutputTag) must not collide with the well-known singleton in a HashSet"); + Assert.DoesNotContain(def, set); } [Fact] @@ -44,21 +43,21 @@ public void Test_OutputTag_GetHashCodeMatchesEquals() OutputTag a = OutputTag.Intermediate; OutputTag b = JsonSerializer.Deserialize("\"intermediate\"", WorkflowsJsonUtilities.DefaultOptions); - a.Equals(b).Should().BeTrue(); - a.GetHashCode().Should().Be(b.GetHashCode()); + Assert.True(a.Equals(b)); + Assert.Equal(b.GetHashCode(), a.GetHashCode()); } [Fact] public void Test_OutputTag_JsonConverter_RoundtripsValueAsString() { string intermediateJson = JsonSerializer.Serialize(OutputTag.Intermediate, WorkflowsJsonUtilities.DefaultOptions); - intermediateJson.Should().Be("\"intermediate\""); + Assert.Equal("\"intermediate\"", intermediateJson); OutputTag back = JsonSerializer.Deserialize("\"intermediate\"", WorkflowsJsonUtilities.DefaultOptions); - back.Should().Be(OutputTag.Intermediate); + Assert.Equal(OutputTag.Intermediate, back); OutputTag fromUnknown = JsonSerializer.Deserialize("\"custom\"", WorkflowsJsonUtilities.DefaultOptions); - fromUnknown.Value.Should().Be("custom"); + Assert.Equal("custom", fromUnknown.Value); } [Fact] @@ -70,8 +69,8 @@ public void Test_OutputTag_ConstructorIsInternal() types: [typeof(string)], modifiers: null); - ctor.Should().NotBeNull("OutputTag(string) must exist as an internal constructor"); - ctor!.IsAssembly.Should().BeTrue("OutputTag(string) must be `internal` so external assemblies cannot synthesize tags"); - ctor.IsPublic.Should().BeFalse(); + Assert.NotNull(ctor); + Assert.True(ctor!.IsAssembly); + Assert.False(ctor.IsPublic); } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/PolymorphicOutputTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/PolymorphicOutputTests.cs index 040975e6a0a..f0fd0d40961 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/PolymorphicOutputTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/PolymorphicOutputTests.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using FluentAssertions; namespace Microsoft.Agents.AI.Workflows.UnitTests; @@ -161,18 +160,18 @@ public async Task ReturningDerivedType_WhenBaseTypeIsDeclared_ShouldSucceedAsync } // Assert - events.Should().NotBeEmpty("workflow should produce events"); + Assert.NotEmpty(events); List outputEvents = events.OfType().ToList(); - outputEvents.Should().ContainSingle("workflow should produce exactly one output event"); + Assert.Single(outputEvents); WorkflowOutputEvent outputEvent = outputEvents.Single(); - outputEvent.Data.Should().BeOfType("output should be the derived type"); - ((DerivedOutput)outputEvent.Data!).Name.Should().Be("DerivedOutput"); + Assert.True((outputEvent.Data) is DerivedOutput); + Assert.Equal("DerivedOutput", ((DerivedOutput)outputEvent.Data!).Name); // Verify no error events List errorEvents = events.OfType().ToList(); - errorEvents.Should().BeEmpty("workflow should not produce error events"); + Assert.Empty(errorEvents ?? []); } /// @@ -195,18 +194,18 @@ public async Task ReturningGrandchildType_WhenBaseTypeIsDeclared_ShouldSucceedAs } // Assert - events.Should().NotBeEmpty("workflow should produce events"); + Assert.NotEmpty(events); List outputEvents = events.OfType().ToList(); - outputEvents.Should().ContainSingle("workflow should produce exactly one output event"); + Assert.Single(outputEvents); WorkflowOutputEvent outputEvent = outputEvents.Single(); - outputEvent.Data.Should().BeOfType("output should be the grandchild type"); - ((GrandchildOutput)outputEvent.Data!).Name.Should().Be("GrandchildOutput"); + Assert.True((outputEvent.Data) is GrandchildOutput); + Assert.Equal("GrandchildOutput", ((GrandchildOutput)outputEvent.Data!).Name); // Verify no error events List errorEvents = events.OfType().ToList(); - errorEvents.Should().BeEmpty("workflow should not produce error events"); + Assert.Empty(errorEvents ?? []); } /// @@ -231,12 +230,12 @@ public async Task ReturningUnrelatedType_WhenBaseTypeIsDeclared_ShouldFailAsync( // Assert: Should have an error event with InvalidOperationException message List errorEvents = events.OfType().ToList(); - errorEvents.Should().ContainSingle("workflow should produce exactly one error event"); + Assert.Single(errorEvents); WorkflowErrorEvent errorEvent = errorEvents.Single(); string errorMessage = errorEvent.Data?.ToString() ?? string.Empty; - errorMessage.Should().Contain("Cannot output object of type UnrelatedOutput"); - errorMessage.Should().Contain("BaseOutput"); + Assert.Contains("Cannot output object of type UnrelatedOutput", errorMessage); + Assert.Contains("BaseOutput", errorMessage); } /// @@ -259,17 +258,17 @@ public async Task ReturningExactType_WhenSameTypeIsDeclared_ShouldSucceedAsync() } // Assert - events.Should().NotBeEmpty("workflow should produce events"); + Assert.NotEmpty(events); List outputEvents = events.OfType().ToList(); - outputEvents.Should().ContainSingle("workflow should produce exactly one output event"); + Assert.Single(outputEvents); WorkflowOutputEvent outputEvent = outputEvents.Single(); - outputEvent.Data.Should().BeOfType("output should be the exact base type"); + Assert.True((outputEvent.Data) is BaseOutput); // Verify no error events List errorEvents = events.OfType().ToList(); - errorEvents.Should().BeEmpty("workflow should not produce error events"); + Assert.Empty(errorEvents ?? []); } #endregion diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/PortableValueTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/PortableValueTests.cs index 86ffed0ab42..62536f920f5 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/PortableValueTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/PortableValueTests.cs @@ -3,7 +3,6 @@ using System; using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.Agents.AI.Workflows.Checkpointing; using Microsoft.Extensions.AI; @@ -24,13 +23,13 @@ private Never() { } [InlineData(3.14)] public async Task Test_PortableValueRoundtripAsync(T value) { - value.Should().NotBeNull(); + Assert.NotNull(value); PortableValue portableValue = new(value); - portableValue.Is(out _).Should().BeFalse(); - portableValue.Is(out T? returnedValue).Should().BeTrue(); - returnedValue.Should().Be(value); + Assert.False(portableValue.Is(out _)); + Assert.True(portableValue.Is(out T? returnedValue)); + Assert.Equal(value, returnedValue); } [Fact] @@ -40,9 +39,9 @@ public async Task Test_PortableValueRoundtripObjectAsync() PortableValue portableValue = new(value); - portableValue.Is(out _).Should().BeFalse(); - portableValue.Is(out ChatMessage? returnedValue).Should().BeTrue(); - returnedValue.Should().Be(value); + Assert.False(portableValue.Is(out _)); + Assert.True(portableValue.Is(out ChatMessage? returnedValue)); + Assert.Equal(value, returnedValue); } [Theory] @@ -52,20 +51,19 @@ public async Task Test_PortableValueRoundtripObjectAsync() [InlineData(3.14)] public async Task Test_DelayedSerializationRoundtripAsync(T value) { - value.Should().NotBeNull(); + Assert.NotNull(value); TestDelayedDeserialization delayed = new(value); PortableValue portableValue = new(delayed); - portableValue.Is(out _).Should().BeFalse(); - portableValue.Is(out object? obj).Should().BeTrue(); - obj.Should().NotBeOfType(); - obj.Should().BeOfType() - .And.Subject.As() - .As().Should().Be(value); + Assert.False(portableValue.Is(out _)); + Assert.True(portableValue.Is(out object? obj)); + Assert.False((obj) is T); + PortableValue nestedPortableValue = Assert.IsType(obj); + Assert.Equal(value, nestedPortableValue.As()); - portableValue.Is(out T? returnedValue).Should().BeTrue(); - returnedValue.Should().Be(value); + Assert.True(portableValue.Is(out T? returnedValue)); + Assert.Equal(value, returnedValue); } [Fact] @@ -76,15 +74,14 @@ public async Task Test_DelayedSerializationRoundtripObjectAsync() TestDelayedDeserialization delayed = new(value); PortableValue portableValue = new(delayed); - portableValue.Is(out _).Should().BeFalse(); - portableValue.Is(out object? obj).Should().BeTrue(); - obj.Should().NotBeOfType(); - obj.Should().BeOfType() - .And.Subject.As() - .As().Should().Be(value); + Assert.False(portableValue.Is(out _)); + Assert.True(portableValue.Is(out object? obj)); + Assert.False((obj) is ChatMessage); + PortableValue nestedPortableValue = Assert.IsType(obj); + Assert.Equal(value, nestedPortableValue.As()); - portableValue.Is(out ChatMessage? returnedValue).Should().BeTrue(); - returnedValue.Should().Be(value); + Assert.True(portableValue.Is(out ChatMessage? returnedValue)); + Assert.Equal(value, returnedValue); } private sealed class TestDelayedDeserialization : IDelayedDeserialization diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/PromptTemplatesTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/PromptTemplatesTests.cs index 8b4760e380c..e5ad68b50b4 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/PromptTemplatesTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/PromptTemplatesTests.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. using System; -using FluentAssertions; using Microsoft.Agents.AI.Workflows.Specialized.Magentic; using Microsoft.Extensions.AI; @@ -72,8 +71,8 @@ public void ProsePrompt_Default_IsEnglish_WithoutLanguageDirective(string prompt string prompt = RenderProsePrompt(context, promptName); // Assert - no language directive by default (built-in English prompts are used as-is). - prompt.Should().NotContain("Write your entire response in"); - prompt.Should().NotContain("Do not use any other language"); + Assert.DoesNotContain("Write your entire response in", prompt); + Assert.DoesNotContain("Do not use any other language", prompt); } [Fact] @@ -86,9 +85,9 @@ public void FactsPrompt_Default_UsesOriginalEnglishHeadings_AndSubstitutesTask() string prompt = context.ToTaskLedgerFactsPrompt(); // Assert - reverted to the original English template (no per-language heading instruction); task substituted. - prompt.Should().Contain("Your answer should use headings:"); - prompt.Should().Contain("GIVEN OR VERIFIED FACTS"); - prompt.Should().Contain(TaskText); + Assert.Contains("Your answer should use headings:", prompt); + Assert.Contains("GIVEN OR VERIFIED FACTS", prompt); + Assert.Contains(TaskText, prompt); } [Fact] @@ -101,10 +100,10 @@ public void ProgressLedgerPrompt_Default_HasSchemaContract_WithoutLanguageDirect string prompt = context.ToProgressLedgerPrompt(); // Assert - schema/routing contract present; no language directive by default. - prompt.Should().Contain("DO NOT OUTPUT ANYTHING OTHER THAN JSON"); - prompt.Should().Contain("next_speaker"); - prompt.Should().Contain("instruction_or_question"); - prompt.Should().NotContain("Do not translate the JSON keys"); + Assert.Contains("DO NOT OUTPUT ANYTHING OTHER THAN JSON", prompt); + Assert.Contains("next_speaker", prompt); + Assert.Contains("instruction_or_question", prompt); + Assert.DoesNotContain("Do not translate the JSON keys", prompt); } [Theory] @@ -118,7 +117,7 @@ public void ProsePrompt_WithResponseLanguage_PinsConcreteLanguage(string promptN string prompt = RenderProsePrompt(context, promptName); // Assert - the concrete language directive is appended after the body. - prompt.Should().Contain("Write your entire response in Esperanto"); + Assert.Contains("Write your entire response in Esperanto", prompt); } [Fact] @@ -131,14 +130,14 @@ public void ProgressLedgerPrompt_WithResponseLanguage_PinsConcreteLanguage_AndPr string prompt = context.ToProgressLedgerPrompt(); // Assert - concrete language pinned for the free-text values... - prompt.Should().Contain(ConcreteLanguageMarker); + Assert.Contains(ConcreteLanguageMarker, prompt); // ...while the JSON-key/next_speaker protections and schema contract remain intact. - prompt.Should().Contain("Do not translate the JSON keys"); - prompt.Should().Contain("must not be translated"); - prompt.Should().Contain("DO NOT OUTPUT ANYTHING OTHER THAN JSON"); - prompt.Should().Contain("next_speaker"); - prompt.Should().Contain("instruction_or_question"); + Assert.Contains("Do not translate the JSON keys", prompt); + Assert.Contains("must not be translated", prompt); + Assert.Contains("DO NOT OUTPUT ANYTHING OTHER THAN JSON", prompt); + Assert.Contains("next_speaker", prompt); + Assert.Contains("instruction_or_question", prompt); } [Fact] @@ -151,10 +150,10 @@ public void FullTaskLedgerPrompt_NeverAppendsLanguageDirective_AndSubstitutesFac string prompt = context.ToTaskLedgerFullPrompt(); // Assert - prompt.Should().NotContain("Write your entire response in"); - prompt.Should().Contain(TaskText); - prompt.Should().Contain(FactsText); - prompt.Should().Contain(PlanText); + Assert.DoesNotContain("Write your entire response in", prompt); + Assert.Contains(TaskText, prompt); + Assert.Contains(FactsText, prompt); + Assert.Contains(PlanText, prompt); } [Fact] @@ -168,9 +167,9 @@ public void PromptOverride_ReplacesBody_AndSubstitutesPlaceholders() string prompt = context.ToTaskLedgerFactsPrompt(); // Assert - the override body is used with placeholders substituted, and the default template is gone. - prompt.Should().Contain("CUSTOM facts request for"); - prompt.Should().Contain(TaskText); - prompt.Should().NotContain("Ken Jennings-level"); + Assert.Contains("CUSTOM facts request for", prompt); + Assert.Contains(TaskText, prompt); + Assert.DoesNotContain("Ken Jennings-level", prompt); } [Fact] @@ -184,9 +183,9 @@ public void PromptOverride_ComposesWith_ResponseLanguage() string prompt = context.ToFinalAnswerPrompt(); // Assert - override body + the concrete language directive appended after it. - prompt.Should().Contain("CUSTOM final answer for"); - prompt.Should().Contain(TaskText); - prompt.Should().Contain(ConcreteLanguageMarker); + Assert.Contains("CUSTOM final answer for", prompt); + Assert.Contains(TaskText, prompt); + Assert.Contains(ConcreteLanguageMarker, prompt); } [Fact] @@ -200,10 +199,10 @@ public void ProgressLedgerOverride_InjectsSchemaViaPlaceholder() string prompt = context.ToProgressLedgerPrompt(); // Assert - the framework injects the JSON schema (keys) into the override via {schema}. - prompt.Should().Contain("CUSTOM ledger for"); - prompt.Should().Contain(TaskText); - prompt.Should().Contain("next_speaker"); - prompt.Should().Contain("instruction_or_question"); + Assert.Contains("CUSTOM ledger for", prompt); + Assert.Contains(TaskText, prompt); + Assert.Contains("next_speaker", prompt); + Assert.Contains("instruction_or_question", prompt); } [Fact] @@ -225,10 +224,10 @@ public void Substitute_DoesNotReExpandInsertedContent() string prompt = context.ToProgressLedgerPrompt(); // Assert - the task's literal {schema}/{team} tokens survive verbatim (not clobbered by later replacements)... - prompt.Should().Contain("Design a {schema} for the {team} data"); + Assert.Contains("Design a {schema} for the {team} data", prompt); // ...while the real template placeholders were still substituted (team description + schema JSON keys). - prompt.Should().Contain("Researcher"); - prompt.Should().Contain("next_speaker"); + Assert.Contains("Researcher", prompt); + Assert.Contains("next_speaker", prompt); } [Fact] @@ -243,20 +242,27 @@ public void DefaultPrompts_AreThePublicMagenticDefaultPrompts() string finalAnswerPrompt = context.ToFinalAnswerPrompt(); // Assert - the rendered prompt is the public default with {task} substituted. - factsPrompt.Should().Be(MagenticDefaultPrompts.TaskLedgerFactsPrompt.Replace("{task}", context.Task)); - finalAnswerPrompt.Should().Be(MagenticDefaultPrompts.FinalAnswerPrompt.Replace("{task}", context.Task)); + Assert.Equal(MagenticDefaultPrompts.TaskLedgerFactsPrompt.Replace("{task}", context.Task), factsPrompt); + Assert.Equal(MagenticDefaultPrompts.FinalAnswerPrompt.Replace("{task}", context.Task), finalAnswerPrompt); } [Fact] public void MagenticDefaultPrompts_ExposeExpectedPlaceholders() { // Assert - the published defaults keep the placeholders callers rely on when tailoring an override. - MagenticDefaultPrompts.TaskLedgerFactsPrompt.Should().Contain("{task}"); - MagenticDefaultPrompts.TaskLedgerFactsUpdatePrompt.Should().Contain("{task}").And.Contain("{old_facts}"); - MagenticDefaultPrompts.TaskLedgerPlanPrompt.Should().Contain("{team}"); - MagenticDefaultPrompts.TaskLedgerPlanUpdatePrompt.Should().Contain("{team}"); - MagenticDefaultPrompts.TaskLedgerFullPrompt.Should().Contain("{task}").And.Contain("{team}").And.Contain("{facts}").And.Contain("{plan}"); - MagenticDefaultPrompts.ProgressLedgerPrompt.Should().Contain("{task}").And.Contain("{team}").And.Contain("{questions}").And.Contain("{schema}"); - MagenticDefaultPrompts.FinalAnswerPrompt.Should().Contain("{task}"); + Assert.Contains("{task}", MagenticDefaultPrompts.TaskLedgerFactsPrompt); + Assert.Contains("{task}", MagenticDefaultPrompts.TaskLedgerFactsUpdatePrompt); + Assert.Contains("{old_facts}", MagenticDefaultPrompts.TaskLedgerFactsUpdatePrompt); + Assert.Contains("{team}", MagenticDefaultPrompts.TaskLedgerPlanPrompt); + Assert.Contains("{team}", MagenticDefaultPrompts.TaskLedgerPlanUpdatePrompt); + Assert.Contains("{task}", MagenticDefaultPrompts.TaskLedgerFullPrompt); + Assert.Contains("{team}", MagenticDefaultPrompts.TaskLedgerFullPrompt); + Assert.Contains("{facts}", MagenticDefaultPrompts.TaskLedgerFullPrompt); + Assert.Contains("{plan}", MagenticDefaultPrompts.TaskLedgerFullPrompt); + Assert.Contains("{task}", MagenticDefaultPrompts.ProgressLedgerPrompt); + Assert.Contains("{team}", MagenticDefaultPrompts.ProgressLedgerPrompt); + Assert.Contains("{questions}", MagenticDefaultPrompts.ProgressLedgerPrompt); + Assert.Contains("{schema}", MagenticDefaultPrompts.ProgressLedgerPrompt); + Assert.Contains("{task}", MagenticDefaultPrompts.FinalAnswerPrompt); } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RepresentationTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RepresentationTests.cs index c86d8efc8aa..38b8cd51c14 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RepresentationTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RepresentationTests.cs @@ -7,7 +7,6 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.Agents.AI.Workflows.Checkpointing; using Microsoft.Agents.AI.Workflows.Sample; using Microsoft.Agents.AI.Workflows.Specialized; @@ -47,7 +46,7 @@ private static async ValueTask RunExecutorBindingInfoMatchTestAsync(ExecutorBind { ExecutorInfo info = binding.ToExecutorInfo(); - info.IsMatch(await binding.CreateInstanceAsync(sessionId: string.Empty)).Should().BeTrue(); + Assert.True(info.IsMatch(await binding.CreateInstanceAsync(sessionId: string.Empty))); } [Fact] @@ -67,7 +66,7 @@ public async Task Test_ExecutorBinding_InfosAsync() int expectedTests = workflowAssembly.GetTypes() .Count(type => type != bindingBaseType && bindingBaseType.IsAssignableFrom(type)); - expectedTests.Should().BePositive(); + Assert.True((expectedTests) > 0); if (expectedTests > testsRun + 1) { @@ -159,7 +158,7 @@ static void RunEdgeInfoMatchTest(Edge edge, Edge? comparatorEdge = null, bool ex comparatorEdge ??= edge; EdgeInfo info = edge.ToEdgeInfo(); - info.IsMatch(comparatorEdge).Should().Be(expect); + Assert.Equal(expect, info.IsMatch(comparatorEdge)); } EdgeId TakeEdgeId() => new(edgeId++); @@ -183,7 +182,7 @@ static void RunWorkflowInfoMatchTest(Workflow workflow, Workflow? comparator = n comparator ??= workflow; WorkflowInfo info = workflow.ToWorkflowInfo(); - info.IsMatch(comparator).Should().Be(expect); + Assert.Equal(expect, info.IsMatch(comparator)); } } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RoundRobinGroupChatManagerTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RoundRobinGroupChatManagerTests.cs index 125d4f1c557..dad8be7143d 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RoundRobinGroupChatManagerTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RoundRobinGroupChatManagerTests.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.Extensions.AI; namespace Microsoft.Agents.AI.Workflows.UnitTests; @@ -25,9 +24,9 @@ public async Task RoundRobinGroupChat_SelectNextAgent_CyclesInOrderAsync() AIAgent second = await manager.SelectNextAgentAsync(history); AIAgent third = await manager.SelectNextAgentAsync(history); - first.Should().BeSameAs(agent1); - second.Should().BeSameAs(agent2); - third.Should().BeSameAs(agent3); + Assert.Same(agent1, first); + Assert.Same(agent2, second); + Assert.Same(agent3, third); } [Fact] @@ -45,7 +44,7 @@ public async Task RoundRobinGroupChat_SelectNextAgent_WrapsAroundAsync() AIAgent wrappedAgent = await manager.SelectNextAgentAsync(history); - wrappedAgent.Should().BeSameAs(agent1, "the manager should wrap around to the first agent after cycling through all agents"); + Assert.Same(agent1, wrappedAgent); } [Fact] @@ -59,11 +58,11 @@ public async Task RoundRobinGroupChat_ShouldTerminate_DefaultBehaviorTerminatesA manager.IterationCount = 2; bool shouldTerminateBefore = await manager.ShouldTerminateAsync(history); - shouldTerminateBefore.Should().BeFalse("the iteration count has not yet reached the maximum"); + Assert.False(shouldTerminateBefore); manager.IterationCount = 3; bool shouldTerminateAt = await manager.ShouldTerminateAsync(history); - shouldTerminateAt.Should().BeTrue("the iteration count has reached the maximum"); + Assert.True(shouldTerminateAt); } [Fact] @@ -80,7 +79,7 @@ public async Task RoundRobinGroupChat_ShouldTerminate_CustomFuncTerminatesEarlyA }; bool shouldTerminate = await manager.ShouldTerminateAsync(history); - shouldTerminate.Should().BeTrue("the custom termination function should cause early termination"); + Assert.True(shouldTerminate); } [Fact] @@ -97,7 +96,7 @@ public async Task RoundRobinGroupChat_ShouldTerminate_CustomFuncDoesNotTerminate }; bool shouldTerminate = await manager.ShouldTerminateAsync(history); - shouldTerminate.Should().BeFalse("the custom termination function should not cause termination when condition is not met"); + Assert.False(shouldTerminate); } [Fact] @@ -116,25 +115,22 @@ public async Task RoundRobinGroupChat_Reset_ResetsIterationCountAndAgentIndexAsy manager.Reset(); - manager.IterationCount.Should().Be(0, "Reset should clear the iteration count"); + Assert.Equal(0, manager.IterationCount); AIAgent afterReset = await manager.SelectNextAgentAsync(history); - afterReset.Should().BeSameAs(agent1, "Reset should cause the next selection to start from the first agent"); + Assert.Same(agent1, afterReset); } [Fact] public void RoundRobinGroupChat_Constructor_ThrowsOnNullAgents() { - FluentActions.Invoking(() => new RoundRobinGroupChatManager(null!)) - .Should().Throw() - .WithParameterName("agents"); + Assert.Equal("agents", Assert.Throws(() => new RoundRobinGroupChatManager(null!)).ParamName); } [Fact] public void RoundRobinGroupChat_Constructor_ThrowsOnEmptyAgents() { - FluentActions.Invoking(() => new RoundRobinGroupChatManager([])) - .Should().Throw(); + Assert.Throws(() => new RoundRobinGroupChatManager([])); } [Fact] @@ -157,14 +153,14 @@ public async Task RoundRobinGroupChat_CheckpointRoundTrip_PreservesIterationCoun await source.CheckpointAsync(sourceContext); RoundRobinGroupChatManager restored = new(agents); - restored.IterationCount.Should().Be(0, "freshly constructed manager has no iteration count"); + Assert.Equal(0, restored.IterationCount); await restored.RestoreCheckpointAsync(sinkContext); - restored.IterationCount.Should().Be(7, "the base hook must rehydrate IterationCount"); + Assert.Equal(7, restored.IterationCount); AIAgent next = await restored.SelectNextAgentAsync(history); - next.Should().BeSameAs(agent2, "the round-robin cursor should resume where the source left off"); + Assert.Same(agent2, next); } [Fact] @@ -183,8 +179,8 @@ public async Task RoundRobinGroupChat_RestoreWithoutCheckpoint_DefaultsToZeroSta await manager.RestoreCheckpointAsync(emptyContext); - manager.IterationCount.Should().Be(0, "restore from an empty checkpoint should clear IterationCount"); + Assert.Equal(0, manager.IterationCount); AIAgent next = await manager.SelectNextAgentAsync(history); - next.Should().BeSameAs(agent1, "restore from an empty checkpoint should reset the cursor to the first agent"); + Assert.Same(agent1, next); } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RouteBuilderTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RouteBuilderTests.cs index a734b82b66b..e5e5d8f5361 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RouteBuilderTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RouteBuilderTests.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.Agents.AI.Workflows.Execution; namespace Microsoft.Agents.AI.Workflows.UnitTests; @@ -79,17 +78,17 @@ public async Task AddHandler_VoidOverloads_RouteExpectedMessageAsync(HandlerOver CallResult? result = await router.RouteMessageAsync("hello", context, cancellationToken: cancellationToken); // Assert - result.Should().NotBeNull(); - result!.IsSuccess.Should().BeTrue(); - result.IsVoid.Should().BeTrue(); - result.Result.Should().BeNull(); - invocation.InvocationCount.Should().Be(1); - invocation.Message.Should().Be("hello"); - invocation.Context.Should().BeSameAs(context); + Assert.NotNull(result); + Assert.True(result!.IsSuccess); + Assert.True(result.IsVoid); + Assert.Null(result.Result); + Assert.Equal(1, invocation.InvocationCount); + Assert.Equal("hello", invocation.Message); + Assert.Same(context, invocation.Context); if (UsesCancellationToken(overload)) { - invocation.CancellationToken.Should().Be(cancellationToken); + Assert.Equal(cancellationToken, invocation.CancellationToken); } } @@ -112,18 +111,18 @@ public async Task AddHandler_ResultOverloads_RouteExpectedMessageAsync(HandlerOv CallResult? result = await router.RouteMessageAsync("hello", context, cancellationToken: cancellationToken); // Assert - result.Should().NotBeNull(); - result!.IsSuccess.Should().BeTrue(); - result.IsVoid.Should().BeFalse(); - result.Result.Should().Be("HELLO"); - router.DefaultOutputTypes.Should().Contain(typeof(string)); - invocation.InvocationCount.Should().Be(1); - invocation.Message.Should().Be("hello"); - invocation.Context.Should().BeSameAs(context); + Assert.NotNull(result); + Assert.True(result!.IsSuccess); + Assert.False(result.IsVoid); + Assert.Equal("HELLO", result.Result); + Assert.Contains(typeof(string), router.DefaultOutputTypes); + Assert.Equal(1, invocation.InvocationCount); + Assert.Equal("hello", invocation.Message); + Assert.Same(context, invocation.Context); if (UsesCancellationToken(overload)) { - invocation.CancellationToken.Should().Be(cancellationToken); + Assert.Equal(cancellationToken, invocation.CancellationToken); } } @@ -147,17 +146,17 @@ public async Task AddCatchAll_VoidOverloads_RouteUnexpectedMessageAsync(HandlerO CallResult? result = await router.RouteMessageAsync(payload, context, cancellationToken: cancellationToken); // Assert - result.Should().NotBeNull(); - result!.IsSuccess.Should().BeTrue(); - result.IsVoid.Should().BeTrue(); - result.Result.Should().BeNull(); - invocation.InvocationCount.Should().Be(1); - invocation.Message.Should().BeEquivalentTo(new PortableValue(payload)); - invocation.Context.Should().BeSameAs(context); + Assert.NotNull(result); + Assert.True(result!.IsSuccess); + Assert.True(result.IsVoid); + Assert.Null(result.Result); + Assert.Equal(1, invocation.InvocationCount); + Assert.Equivalent(new PortableValue(payload), invocation.Message); + Assert.Same(context, invocation.Context); if (UsesCancellationToken(overload)) { - invocation.CancellationToken.Should().Be(cancellationToken); + Assert.Equal(cancellationToken, invocation.CancellationToken); } } @@ -181,17 +180,17 @@ public async Task AddCatchAll_ResultOverloads_RouteUnexpectedMessageAsync(Handle CallResult? result = await router.RouteMessageAsync(payload, context, cancellationToken: cancellationToken); // Assert - result.Should().NotBeNull(); - result!.IsSuccess.Should().BeTrue(); - result.IsVoid.Should().BeFalse(); - result.Result.Should().Be("HELLO"); - invocation.InvocationCount.Should().Be(1); - invocation.Message.Should().BeEquivalentTo(new PortableValue(payload)); - invocation.Context.Should().BeSameAs(context); + Assert.NotNull(result); + Assert.True(result!.IsSuccess); + Assert.False(result.IsVoid); + Assert.Equal("HELLO", result.Result); + Assert.Equal(1, invocation.InvocationCount); + Assert.Equivalent(new PortableValue(payload), invocation.Message); + Assert.Same(context, invocation.Context); if (UsesCancellationToken(overload)) { - invocation.CancellationToken.Should().Be(cancellationToken); + Assert.Equal(cancellationToken, invocation.CancellationToken); } } @@ -221,18 +220,18 @@ public async Task AddHandlerUntyped_VoidAndResultOverloads_RouteExpectedMessageA CallResult? typedResult = await router.RouteMessageAsync(41, context, cancellationToken: cancellationToken); // Assert - voidResult.Should().NotBeNull(); - voidResult!.IsVoid.Should().BeTrue(); - voidInvocation.Message.Should().Be("hello"); - voidInvocation.Context.Should().BeSameAs(context); - voidInvocation.CancellationToken.Should().Be(cancellationToken); - - typedResult.Should().NotBeNull(); - typedResult!.Result.Should().Be(42); - router.DefaultOutputTypes.Should().Contain(typeof(int)); - resultInvocation.Message.Should().Be(41); - resultInvocation.Context.Should().BeSameAs(context); - resultInvocation.CancellationToken.Should().Be(cancellationToken); + Assert.NotNull(voidResult); + Assert.True(voidResult!.IsVoid); + Assert.Equal("hello", voidInvocation.Message); + Assert.Same(context, voidInvocation.Context); + Assert.Equal(cancellationToken, voidInvocation.CancellationToken); + + Assert.NotNull(typedResult); + Assert.Equal(42, typedResult!.Result); + Assert.Contains(typeof(int), router.DefaultOutputTypes); + Assert.Equal(41, resultInvocation.Message); + Assert.Same(context, resultInvocation.Context); + Assert.Equal(cancellationToken, resultInvocation.CancellationToken); } [Fact] @@ -245,8 +244,7 @@ public void AddHandler_ForPortableValue_ThrowsInvalidOperationException() Action act = () => routeBuilder.AddHandler((message, context) => { }); // Assert - act.Should().Throw() - .WithMessage("*Use AddCatchAll()*"); + Assert.Contains("Use AddCatchAll()", (Assert.Throws(act)).Message); } [Fact] @@ -260,8 +258,7 @@ public void AddHandler_DuplicateRegistrationWithoutOverwrite_ThrowsArgumentExcep Action act = () => routeBuilder.AddHandler((message, context) => { }); // Assert - act.Should().Throw() - .WithMessage("*already registered*"); + Assert.Contains("already registered", (Assert.Throws(act)).Message); } [Fact] @@ -274,8 +271,7 @@ public void AddHandler_OverwriteWithoutExistingRegistration_ThrowsArgumentExcept Action act = () => routeBuilder.AddHandler((message, context) => { }, overwrite: true); // Assert - act.Should().Throw() - .WithMessage("*has not yet been registered*"); + Assert.Contains("has not yet been registered", (Assert.Throws(act)).Message); } [Fact] @@ -292,7 +288,7 @@ public async Task AddHandler_OverwriteExistingRegistration_RoutesUpdatedHandlerA _ = await router.RouteMessageAsync("hello", context); // Assert - context.SentMessages.Should().ContainSingle().Which.Should().Be("second"); + Assert.Equal("second", Assert.Single(context.SentMessages)); } [Fact] @@ -306,8 +302,7 @@ public void AddCatchAll_DuplicateRegistrationWithoutOverwrite_ThrowsInvalidOpera Action act = () => routeBuilder.AddCatchAll((message, context) => { }); // Assert - act.Should().Throw() - .WithMessage("*already registered*"); + Assert.Contains("already registered", (Assert.Throws(act)).Message); } [Fact] @@ -324,7 +319,7 @@ public async Task AddCatchAll_OverwriteExistingRegistration_RoutesUpdatedHandler _ = await router.RouteMessageAsync(new TestPayload("hello"), context); // Assert - context.SentMessages.Should().ContainSingle().Which.Should().Be("second"); + Assert.Equal("second", Assert.Single(context.SentMessages)); } [Fact] @@ -337,8 +332,7 @@ public void AddPortHandler_WithoutExternalRequestContext_ThrowsInvalidOperationE Action act = () => routeBuilder.AddPortHandler("port", (response, context, cancellationToken) => default, out _); // Assert - act.Should().Throw() - .WithMessage("*external request context is required*"); + Assert.Contains("external request context is required", (Assert.Throws(act)).Message); } [Fact] @@ -363,15 +357,15 @@ public async Task AddPortHandler_RoutesMatchingExternalResponseAsync() CallResult? result = await router.RouteMessageAsync(response, context, cancellationToken: cancellationToken); // Assert - externalRequestContext.RegisteredPorts.Should().ContainSingle(port => port.Id == "port"); - externalRequestContext.PostedRequests.Should().ContainSingle(request => request.RequestId == "req-1"); - result.Should().NotBeNull(); - result!.IsSuccess.Should().BeTrue(); - result.Result.Should().BeSameAs(response); - invocation.InvocationCount.Should().Be(1); - invocation.Message.Should().Be(42); - invocation.Context.Should().BeSameAs(context); - invocation.CancellationToken.Should().Be(cancellationToken); + Assert.Single(externalRequestContext.RegisteredPorts); + Assert.Single(externalRequestContext.PostedRequests); + Assert.NotNull(result); + Assert.True(result!.IsSuccess); + Assert.Same(response, result.Result); + Assert.Equal(1, invocation.InvocationCount); + Assert.Equal(42, invocation.Message); + Assert.Same(context, invocation.Context); + Assert.Equal(cancellationToken, invocation.CancellationToken); } [Fact] @@ -388,10 +382,10 @@ public async Task AddPortHandler_UnknownPort_ReturnsExceptionResultAsync() CallResult? result = await router.RouteMessageAsync(request.CreateResponse(42), new TestWorkflowContext("executor")); // Assert - result.Should().NotBeNull(); - result!.IsSuccess.Should().BeFalse(); - result.Exception.Should().BeOfType(); - result.Exception!.Message.Should().Contain("Unknown port"); + Assert.NotNull(result); + Assert.False(result!.IsSuccess); + Assert.True((result.Exception) is InvalidOperationException); + Assert.Contains("Unknown port", result.Exception!.Message); } private static void RegisterVoidHandler(RouteBuilder routeBuilder, HandlerInvocation invocation, HandlerOverload overload) diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/05_Simple_Workflow_Checkpointing.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/05_Simple_Workflow_Checkpointing.cs index 2d26062c78a..f203f72c58b 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/05_Simple_Workflow_Checkpointing.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/05_Simple_Workflow_Checkpointing.cs @@ -5,7 +5,6 @@ using System.IO; using System.Threading; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.Agents.AI.Workflows.InProc; namespace Microsoft.Agents.AI.Workflows.Sample; @@ -33,8 +32,8 @@ await environment.WithCheckpointing(checkpointManager) string? result = await RunStreamToHaltOrMaxStepAsync(maxStep: 6).ConfigureAwait(false); - result.Should().BeNull(); - checkpoints.Should().HaveCount(6, "we should have two checkpoints, one for each step"); + Assert.Null(result); + Assert.Equal(6, checkpoints.Count); CheckpointInfo targetCheckpoint = checkpoints[2]; @@ -60,12 +59,12 @@ await environment.WithCheckpointing(checkpointManager) checkpoints.Clear(); result = await RunStreamToHaltOrMaxStepAsync().ConfigureAwait(false); - result.Should().NotBeNull(); + Assert.NotNull(result); // Depending on the timing of the response with respect to the underlying workflow // we may end up with an extra superstep in between. - checkpoints.Should().HaveCountGreaterThanOrEqualTo(6) - .And.HaveCountLessThanOrEqualTo(7); + Assert.True(checkpoints.Count >= 6); + Assert.True(checkpoints.Count <= 7); cancellationSource.Dispose(); diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/08_Subworkflow_Simple.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/08_Subworkflow_Simple.cs index 32c63ae4526..8d0f6404365 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/08_Subworkflow_Simple.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/08_Subworkflow_Simple.cs @@ -7,7 +7,6 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -using FluentAssertions; namespace Microsoft.Agents.AI.Workflows.Sample; @@ -62,15 +61,15 @@ public static async ValueTask> RunAsync(TextWriter wr Assert.Fail(errorBuilder.ToString()); } - status.Should().Be(RunStatus.Idle); + Assert.Equal(RunStatus.Idle, status); WorkflowOutputEvent? maybeOutput = workflowRun.OutgoingEvents.OfType() .SingleOrDefault(); - maybeOutput.Should().NotBeNull("the workflow should have produced an output event"); + Assert.NotNull(maybeOutput); List? maybeResults = maybeOutput.As>(); - maybeResults.Should().NotBeNull("the output event should contain the results"); + Assert.NotNull(maybeResults); List results = maybeResults; results.Sort((left, right) => StringComparer.Ordinal.Compare(left.TaskId, right.TaskId)); diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/09_Subworkflow_ExternalRequest.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/09_Subworkflow_ExternalRequest.cs index eca800594a3..c62a468ee24 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/09_Subworkflow_ExternalRequest.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/09_Subworkflow_ExternalRequest.cs @@ -7,7 +7,6 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -using FluentAssertions; namespace Microsoft.Agents.AI.Workflows.Sample; @@ -193,7 +192,7 @@ public static async ValueTask> RunAsync(TextWriter writer, RunStatus part1Status = ExpectedResponsesPart2.Length > 0 ? RunStatus.PendingRequests : RunStatus.Idle; runStatus = await workflowRun.GetStatusAsync(); - runStatus.Should().Be(part1Status); + Assert.Equal(part1Status, runStatus); List finishedRequests = []; List resourceRequests = []; @@ -224,34 +223,34 @@ public static async ValueTask> RunAsync(TextWriter writer, } finishedRequests.Sort((left, right) => StringComparer.Ordinal.Compare(left.Id, right.Id)); - finishedRequests.Should().HaveCount(ExpectedResponsesPart1.Count) - .And.ContainInOrder(ExpectedResponsesPart1); + Assert.Equal(ExpectedResponsesPart1.Count, finishedRequests.Count); + Assert.Equal(ExpectedResponsesPart1, finishedRequests); int externalResourceRequests = ExpectedResponsesPart2.Count(finishedRequest => finishedRequest.ResourceResponse != null); int externalPolicyRequests = ExpectedResponsesPart2.Count(finishedRequest => finishedRequest.PolicyResponse != null); - resourceRequests.Should().HaveCount(externalResourceRequests); - policyRequests.Should().HaveCount(externalPolicyRequests); + Assert.Equal(externalResourceRequests, resourceRequests.Count); + Assert.Equal(externalPolicyRequests, policyRequests.Count); List responses = []; foreach (ExternalRequest request in resourceRequests) { ResourceRequest resourceRequest = request.Data.As()!; - resourceRequest.Id.Should().BeOneOf(ResourceMissIds); + Assert.Contains(resourceRequest.Id, ResourceMissIds); responses.Add(request.CreateResponse(Part2FinishedResponses[resourceRequest.Id].ResourceResponse!)); } foreach (ExternalRequest request in policyRequests) { PolicyCheckRequest policyRequest = request.Data.As()!; - policyRequest.Id.Should().BeOneOf(PolicyMissIds); + Assert.Contains(policyRequest.Id, PolicyMissIds); responses.Add(request.CreateResponse(Part2FinishedResponses[policyRequest.Id].PolicyResponse!)); } if (ExpectedResponsesPart2.Length == 0) { - responses.Should().BeEmpty(); + Assert.Empty(responses ?? []); return results; } @@ -273,7 +272,7 @@ public static async ValueTask> RunAsync(TextWriter writer, Assert.Fail(errorBuilder.ToString()); } - runStatus.Should().Be(RunStatus.Idle); + Assert.Equal(RunStatus.Idle, runStatus); results = finishedRequests; @@ -284,8 +283,8 @@ public static async ValueTask> RunAsync(TextWriter writer, .ToList(); finishedRequests.Sort((left, right) => StringComparer.Ordinal.Compare(left.Id, right.Id)); - finishedRequests.Should().HaveCount(ExpectedResponsesPart2.Length) - .And.ContainInOrder(ExpectedResponsesPart2); + Assert.Equal(ExpectedResponsesPart2.Length, finishedRequests.Count); + Assert.Equal(ExpectedResponsesPart2, finishedRequests); results.AddRange(finishedRequests); return results; diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/SampleSmokeTest.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/SampleSmokeTest.cs index c0be892e24e..644393666c8 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/SampleSmokeTest.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/SampleSmokeTest.cs @@ -6,7 +6,6 @@ using System.Linq; using System.Text.Json; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.Agents.AI.Workflows.InProc; using Microsoft.Agents.AI.Workflows.Sample; @@ -254,7 +253,7 @@ Action CreateValidator(string textToProcess, int index) ChatCount: textToProcess.Length ); - result.Should().Be(expected); + Assert.Equal(expected, result); }; } } @@ -275,7 +274,7 @@ internal async Task Test_RunSample_Step9Async(ExecutionEnvironment environment) /// callers must observe a stable terminal status and never a transient /// . Step9 is the canonical multi-response resume /// sample; prior to the fix in , - /// its `runStatus.Should().Be(RunStatus.Idle)` assertion failed intermittently + /// its run status assertion failed intermittently /// on roughly 1-in-10 iterations under InProcess_OffThread. /// [Fact] @@ -307,7 +306,7 @@ internal async Task Test_RunSample_Step10Async(ExecutionEnvironment environment) Assert.Collection(lines, inputs.Select(CreateValidator).ToArray()); - Action CreateValidator(string expected) => actual => actual.Should().Be($"Echo: {expected}"); + Action CreateValidator(string expected) => actual => Assert.Equal($"Echo: {expected}", actual); } [Theory] @@ -334,7 +333,7 @@ internal async Task Test_RunSample_Step11Async(ExecutionEnvironment environment) Assert.Collection(lines, expected.Select(CreateValidator).ToArray()); - Action CreateValidator(string expected) => actual => actual.Should().Be(expected); + Action CreateValidator(string expected) => actual => Assert.Equal(expected, actual); } public class Step12ExpectedOutputCalculator(int agentCount) @@ -357,7 +356,7 @@ public void ProcessInput(string newInput) int agentBookmark = this._bookmarks[i]; int count = this._history.Count - agentBookmark; - count.Should().BeGreaterThanOrEqualTo(0); + Assert.True((count) >= (0)); foreach (string input in this._history.Skip(agentBookmark).ToList()) { @@ -436,7 +435,7 @@ internal async Task Test_RunSample_Step12Async(ExecutionEnvironment environment) Assert.Collection(lines, expected.Select(CreateValidator).ToArray()); - Action CreateValidator(string expected) => actual => actual.Should().Be(expected); + Action CreateValidator(string expected) => actual => Assert.Equal(expected, actual); } [Theory] @@ -523,7 +522,7 @@ internal async Task Test_RunSample_Step14_SharedState_WorksWithinSubworkflowAsyn // Act & Assert - All executors inside the subworkflow should share state using StringWriter writer = new(); int result = await Step14EntryPoint.RunSubworkflowInternalStateAsync(Text, writer, executionEnvironment); - result.Should().Be(expectedCharCount, "executors within subworkflow should share state correctly"); + Assert.Equal(expectedCharCount, result); } /// @@ -547,14 +546,14 @@ internal async Task Test_RunSample_Step14a_SharedState_IsolatedAcrossSubworkflow // Assert - Currently, state is isolated across subworkflow boundaries (issue #2419) // The subworkflow executor cannot see state written by the parent workflow - error.Should().NotBeNull("state written in parent workflow is not visible in subworkflow"); + Assert.NotNull(error); // The exception may be wrapped in TargetInvocationException, so check inner exception too Exception actualError = error is System.Reflection.TargetInvocationException tie && tie.InnerException != null ? tie.InnerException : error; - actualError.Should().BeOfType(); + Assert.True((actualError) is InvalidOperationException); } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/SequentialWorkflowBuilderTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/SequentialWorkflowBuilderTests.cs index ccb6bdc6ec1..0885ea81de4 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/SequentialWorkflowBuilderTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/SequentialWorkflowBuilderTests.cs @@ -6,7 +6,6 @@ using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.Agents.AI.Workflows.UnitTests.Futures; using Microsoft.Extensions.AI; @@ -81,12 +80,12 @@ public async Task Test_SequentialWorkflowBuilder_DefaultNextAgentReceivesFullCon _ = await OrchestrationTestHelpers.RunWorkflowAsync(workflow, [new ChatMessage(ChatRole.User, "start")]); - second.MessagesSeen.Should().NotBeNull(); - second.MessagesSeen.Should().HaveCount(2); - second.MessagesSeen![0].Role.Should().Be(ChatRole.User); - second.MessagesSeen[0].Text.Should().Be("start"); - second.MessagesSeen[1].Role.Should().Be(ChatRole.User); - second.MessagesSeen[1].Text.Should().Be("step-one"); + Assert.NotNull(second.MessagesSeen); + Assert.Equal(2, second.MessagesSeen.Count); + Assert.Equal(ChatRole.User, second.MessagesSeen![0].Role); + Assert.Equal("start", second.MessagesSeen[0].Text); + Assert.Equal(ChatRole.User, second.MessagesSeen[1].Role); + Assert.Equal("step-one", second.MessagesSeen[1].Text); } [Fact] @@ -101,10 +100,10 @@ public async Task Test_SequentialWorkflowBuilder_WithChainOnlyAgentResponses_Nex _ = await OrchestrationTestHelpers.RunWorkflowAsync(workflow, [new ChatMessage(ChatRole.User, "start")]); - second.MessagesSeen.Should().NotBeNull(); - second.MessagesSeen.Should().ContainSingle(); - second.MessagesSeen![0].Role.Should().Be(ChatRole.User); - second.MessagesSeen[0].Text.Should().Be("step-one"); + Assert.NotNull(second.MessagesSeen); + Assert.Single(second.MessagesSeen); + Assert.Equal(ChatRole.User, second.MessagesSeen![0].Role); + Assert.Equal("step-one", second.MessagesSeen[0].Text); } [Fact] @@ -117,10 +116,8 @@ public void Test_SequentialWorkflowBuilder_DefaultDesignationsMatchSpec() .Build(); Dictionary> designations = workflow.OutputExecutors; - designations.Where(kvp => kvp.Value.Count == 0) - .Should().ContainSingle("OutputMessagesExecutor is the sole terminal output by default"); - designations.Where(kvp => kvp.Value.Contains(OutputTag.Intermediate)) - .Should().HaveCount(3, "every pipeline agent is designated intermediate by default"); + Assert.Single(designations, kvp => kvp.Value.Count == 0); + Assert.Equal(3, (designations.Where(kvp => kvp.Value.Contains(OutputTag.Intermediate)))?.Count()); } [Fact] @@ -137,12 +134,9 @@ public void Test_SequentialWorkflowBuilder_ExplicitDesignationsReplaceDefaults() Dictionary> designations = workflow.OutputExecutors; - designations.Should().HaveCount(2, - "only the two explicitly-designated agents land on the inner builder; the end default is suppressed"); - designations.Values.Where(tags => tags.Count == 0) - .Should().ContainSingle("agent1 is the only terminal designation"); - designations.Values.Where(tags => tags.Contains(OutputTag.Intermediate)) - .Should().ContainSingle("agent2 is the only intermediate designation"); + Assert.Equal(2, designations.Count); + Assert.Single(designations.Values, tags => tags.Count == 0); + Assert.Single(designations.Values, tags => tags.Contains(OutputTag.Intermediate)); } [Fact] @@ -155,7 +149,7 @@ public void Test_SequentialWorkflowBuilder_DesignationForNonParticipantThrows() .WithIntermediateOutputFrom([stranger]); Action build = () => builder.Build(); - build.Should().Throw().WithMessage("*stranger*"); + Assert.Contains("stranger", (Assert.Throws(build)).Message); } [Fact] @@ -165,7 +159,7 @@ public void Test_SequentialWorkflowBuilder_WithNamePropagatesToWorkflow() .WithName("named-sequential") .Build(); - workflow.Name.Should().Be("named-sequential"); + Assert.Equal("named-sequential", workflow.Name); } [Fact] @@ -175,7 +169,7 @@ public void Test_SequentialWorkflowBuilder_WithDescriptionPropagatesToWorkflow() .WithDescription("describes the sequential pipeline") .Build(); - workflow.Description.Should().Be("describes the sequential pipeline"); + Assert.Equal("describes the sequential pipeline", workflow.Description); } private sealed class CapturingAgent(string name, string responseText) : AIAgent @@ -256,11 +250,9 @@ public async Task Test_SequentialWorkflowBuilder_AsAgent_OnlyTerminalDesignation .Select(n => n!) .ToHashSet(); - authoredBy.Should().Contain("agent3", "the terminal agent must surface"); - authoredBy.Should().NotContain("agent1", - "the intermediate agent must not surface when only the terminal is designated"); - authoredBy.Should().NotContain("agent2", - "the intermediate agent must not surface when only the terminal is designated"); + Assert.Contains("agent3", authoredBy); + Assert.DoesNotContain("agent1", authoredBy); + Assert.DoesNotContain("agent2", authoredBy); } } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/SpecializedExecutorSmokeTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/SpecializedExecutorSmokeTests.cs index 4e5db48c3b8..c0f7df812e1 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/SpecializedExecutorSmokeTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/SpecializedExecutorSmokeTests.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.Agents.AI.Workflows.Checkpointing; using Microsoft.Agents.AI.Workflows.Execution; using Microsoft.Agents.AI.Workflows.Specialized; @@ -88,14 +87,14 @@ public async Task Test_AIAgentStreamingMessage_AggregationAsync() await host.TakeTurnAsync(new TurnToken(emitEvents: true), collectingContext); // The first empty message is skipped. - collectingContext.Updates.Should().HaveCount(MessageStrings.Length - 1); + Assert.Equal(MessageStrings.Length - 1, collectingContext.Updates.Count); for (int i = 1; i < MessageStrings.Length; i++) { string expectedText = MessageStrings[i]; ChatMessage collected = collectingContext.Updates[i - 1]; - collected.Text.Should().Be(expectedText); + Assert.Equal(expectedText, collected.Text); } } @@ -112,15 +111,15 @@ public async Task Test_AIAgent_ExecutorId_Use_Agent_NameAsync() // Verify that the agent host executor registration IDs in the workflow definition // match the agent names when agent names are provided. // The property DisplayName falls back to using the agent ID when Name is not set. - agentA.GetDescriptiveId().Should().Contain(AgentAName); - agentB.GetDescriptiveId().Should().Contain(AgentBName); - definition.Executors[agentA.GetDescriptiveId()].ExecutorId.Should().Be(agentA.GetDescriptiveId()); - definition.Executors[agentB.GetDescriptiveId()].ExecutorId.Should().Be(agentB.GetDescriptiveId()); + Assert.Contains(AgentAName, agentA.GetDescriptiveId()); + Assert.Contains(AgentBName, agentB.GetDescriptiveId()); + Assert.Equal(agentA.GetDescriptiveId(), definition.Executors[agentA.GetDescriptiveId()].ExecutorId); + Assert.Equal(agentB.GetDescriptiveId(), definition.Executors[agentB.GetDescriptiveId()].ExecutorId); // This will create an instance of the start agent and verify that the ID // of the executor instance matches the ID of the registration. var protocolDescriptor = await workflow.DescribeProtocolAsync(); - protocolDescriptor.Accepts.Should().Contain(typeof(ChatMessage)); + Assert.Contains(typeof(ChatMessage), protocolDescriptor.Accepts); } [Fact] @@ -134,14 +133,14 @@ public async Task Test_AIAgent_ExecutorId_Use_Agent_ID_When_Name_Not_ProvidedAsy // Verify that the agent host executor registration IDs in the workflow definition // match the agent IDs when agent names are not provided. // The property DisplayName falls back to using the agent ID when Name is not set. - agentA.GetDescriptiveId().Should().Contain(agentA.Id); - agentB.GetDescriptiveId().Should().Contain(agentB.Id); - definition.Executors[agentA.GetDescriptiveId()].ExecutorId.Should().Be(agentA.GetDescriptiveId()); - definition.Executors[agentB.GetDescriptiveId()].ExecutorId.Should().Be(agentB.GetDescriptiveId()); + Assert.Contains(agentA.Id, agentA.GetDescriptiveId()); + Assert.Contains(agentB.Id, agentB.GetDescriptiveId()); + Assert.Equal(agentA.GetDescriptiveId(), definition.Executors[agentA.GetDescriptiveId()].ExecutorId); + Assert.Equal(agentB.GetDescriptiveId(), definition.Executors[agentB.GetDescriptiveId()].ExecutorId); // This will create an instance of the start agent and verify that the ID // of the executor instance matches the ID of the registration. var protocolDescriptor = await workflow.DescribeProtocolAsync(); - protocolDescriptor.Accepts.Should().Contain(typeof(ChatMessage)); + Assert.Contains(typeof(ChatMessage), protocolDescriptor.Accepts); } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/StateKeyObjectTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/StateKeyObjectTests.cs index 16b94d831e5..10595858608 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/StateKeyObjectTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/StateKeyObjectTests.cs @@ -1,7 +1,4 @@ -// Copyright (c) Microsoft. All rights reserved. - -using FluentAssertions; -using Microsoft.Agents.AI.Workflows.Execution; +using Microsoft.Agents.AI.Workflows.Execution; namespace Microsoft.Agents.AI.Workflows.UnitTests; @@ -90,8 +87,8 @@ public void Test_UpdateKey_IsMatchingScope() static void ValidateMatch(UpdateKey key, ScopeId scope, bool expectedStrict, bool expectedLoose) { - key.IsMatchingScope(scope, strict: true).Should().Be(expectedStrict); - key.IsMatchingScope(scope, strict: false).Should().Be(expectedLoose); + Assert.Equal(expectedStrict, key.IsMatchingScope(scope, strict: true)); + Assert.Equal(expectedLoose, key.IsMatchingScope(scope, strict: false)); } } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/StateManagerTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/StateManagerTests.cs index 2d81a2ef53a..3f64d6431f6 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/StateManagerTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/StateManagerTests.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.Agents.AI.Workflows.Checkpointing; using Microsoft.Agents.AI.Workflows.Execution; using Microsoft.Extensions.AI; @@ -39,10 +38,10 @@ private static async Task RunScopeKeysTestAsync(string? scopeName, bool isShared // Assert baseline: neither executor sees any keys HashSet selfKeys = await manager.ReadKeysAsync(sharedScopeSelfView); - selfKeys.Should().BeEmpty("there should be no keys in an empty StateManager"); + Assert.Empty(selfKeys ?? []); HashSet otherKeys = await manager.ReadKeysAsync(sharedScopeOtherView); - otherKeys.Should().BeEmpty("there should be no keys in an empty StateManager"); + Assert.Empty(otherKeys ?? []); // Act 1: Write a key from the self executor's view of the shared scope @@ -50,28 +49,27 @@ private static async Task RunScopeKeysTestAsync(string? scopeName, bool isShared // Assert 1: The self executor should see the key immediately, but the other executor should not selfKeys = await manager.ReadKeysAsync(sharedScopeSelfView); - selfKeys.SetEquals(ExpectedAfterWrite).Should().BeTrue("writes should be visible immediately to the writing executor"); + Assert.True(selfKeys.SetEquals(ExpectedAfterWrite)); otherKeys = await manager.ReadKeysAsync(sharedScopeOtherView); - otherKeys.Should().BeEmpty(isSharedScope ? "writes should not be visible to other executors until published" - : "writes to private scopes should not be visible across executors"); + Assert.Empty(otherKeys ?? []); // Act 2: Publish the updates await manager.PublishUpdatesAsync(tracer: null); // Assert 2: Both executors should see the key now, if sharedScope selfKeys = await manager.ReadKeysAsync(sharedScopeSelfView); - selfKeys.SetEquals(ExpectedAfterWrite).Should().BeTrue("published writes should be visible to all executors"); + Assert.True(selfKeys.SetEquals(ExpectedAfterWrite)); otherKeys = await manager.ReadKeysAsync(sharedScopeOtherView); if (isSharedScope) { - otherKeys.SetEquals(ExpectedAfterWrite).Should().BeTrue("published writes should be visible to all executors"); + Assert.True(otherKeys.SetEquals(ExpectedAfterWrite)); } else { - otherKeys.Should().BeEmpty("writes to private scopes should not be visible across executors"); + Assert.Empty(otherKeys ?? []); } // Act 3: Clear the state from the self executor's view of the shared scope @@ -79,16 +77,16 @@ private static async Task RunScopeKeysTestAsync(string? scopeName, bool isShared // Assert 3: The self executor should not see the key immediately, but the other executor should still see it if sharedScope selfKeys = await manager.ReadKeysAsync(sharedScopeSelfView); - selfKeys.Should().BeEmpty("deletes should be visible immediately to the writing executor"); + Assert.Empty(selfKeys ?? []); otherKeys = await manager.ReadKeysAsync(sharedScopeOtherView); if (isSharedScope) { - otherKeys.SetEquals(ExpectedAfterWrite).Should().BeTrue("published writes should be visible to all executors"); + Assert.True(otherKeys.SetEquals(ExpectedAfterWrite)); } else { - otherKeys.Should().BeEmpty("writes to private scopes should not be visible across executors"); + Assert.Empty(otherKeys ?? []); } // Act 4: Publish the updates @@ -96,11 +94,10 @@ private static async Task RunScopeKeysTestAsync(string? scopeName, bool isShared // Assert 4: Neither executor should see the key now selfKeys = await manager.ReadKeysAsync(sharedScopeSelfView); - selfKeys.Should().BeEmpty("published deletes should be visible to all executors"); + Assert.Empty(selfKeys ?? []); otherKeys = await manager.ReadKeysAsync(sharedScopeOtherView); - otherKeys.Should().BeEmpty(isSharedScope ? "published deletes should be visible to all executors" - : "writes to private scopes should not be visible across executors"); + Assert.Empty(otherKeys ?? []); } [Fact] @@ -128,132 +125,125 @@ private static async Task RunValueLifecycleTestAsync(string? scopeName, bool isS ScopeId scopeSelfView = new(SelfExecutorId, scopeName); ScopeId scopeOtherView = new(OtherExecutorId, scopeName); - isSharedScope.Should().Be(scopeSelfView == scopeOtherView); + Assert.Equal(scopeSelfView == scopeOtherView, isSharedScope); // Assert baseline: neither executor sees any keys or values string? selfValue1 = await manager.ReadStateAsync(scopeSelfView, Key1); string? selfValue2 = await manager.ReadStateAsync(scopeSelfView, Key2); - selfValue1.Should().BeNull("there should be no values in an empty StateManager"); - selfValue2.Should().BeNull("there should be no values in an empty StateManager"); + Assert.Null(selfValue1); + Assert.Null(selfValue2); string? otherValue1 = await manager.ReadStateAsync(scopeOtherView, Key1); string? otherValue2 = await manager.ReadStateAsync(scopeOtherView, Key2); - otherValue1.Should().BeNull("there should be no values in an empty StateManager"); - otherValue2.Should().BeNull("there should be no values in an empty StateManager"); + Assert.Null(otherValue1); + Assert.Null(otherValue2); // Act 1: Write a value from the self executor's view of the shared scope await manager.WriteStateAsync(scopeSelfView, Key1, Value1); // Assert 1: The self executor should see the value immediately, but the other executor should not selfValue1 = await manager.ReadStateAsync(scopeSelfView, Key1); - selfValue1.Should().Be(Value1, "writes should be visible immediately to the writing executor"); + Assert.Equal(Value1, selfValue1); selfValue2 = await manager.ReadStateAsync(scopeSelfView, Key2); - selfValue2.Should().BeNull("uninvolved keys' state/value should not change after a write"); + Assert.Null(selfValue2); otherValue1 = await manager.ReadStateAsync(scopeOtherView, Key1); - otherValue1.Should().BeNull(isSharedScope ? "writes should not be visible to other executors until published (key1: written by self, read by other)" - : "writes to private scopes should not be visible across executors"); + Assert.Null(otherValue1); otherValue2 = await manager.ReadStateAsync(scopeOtherView, Key2); - otherValue2.Should().BeNull("uninvolved keys' state/value should not change after a write"); + Assert.Null(otherValue2); // Act 2: Write a value from the other executor's view of the shared scope await manager.WriteStateAsync(scopeOtherView, Key2, Value2); // Assert 2: The other executor should see the value immediately, but the self executor should not selfValue1 = await manager.ReadStateAsync(scopeSelfView, Key1); - selfValue1.Should().Be(Value1, "uninvolved keys' state/value should not change after a write"); + Assert.Equal(Value1, selfValue1); selfValue2 = await manager.ReadStateAsync(scopeSelfView, Key2); - selfValue2.Should().BeNull(isSharedScope ? "writes should not be visible to other executors until published (key2: written by other, read by self)" - : "writes to private scopes should not be visible across executors"); + Assert.Null(selfValue2); otherValue1 = await manager.ReadStateAsync(scopeOtherView, Key1); - otherValue1.Should().BeNull(isSharedScope ? "writes should not be visible to other executors until published (key1: written by self, read by other)" - : "writes to private scopes should not be visible across executors"); + Assert.Null(otherValue1); otherValue2 = await manager.ReadStateAsync(scopeOtherView, Key2); - otherValue2.Should().Be(Value2, "writes should be visible immediately to the writing executor"); + Assert.Equal(Value2, otherValue2); // Act 3: Publish the updates await manager.PublishUpdatesAsync(tracer: null); // Assert 3: Both executors should see both values now, if the scope is shared selfValue1 = await manager.ReadStateAsync(scopeSelfView, Key1); - selfValue1.Should().Be(Value1, "published writes should be visible to all executors (key1: written by self, read by self)"); + Assert.Equal(Value1, selfValue1); selfValue2 = await manager.ReadStateAsync(scopeSelfView, Key2); if (isSharedScope) { - selfValue2.Should().Be(Value2, "published writes should be visible to all executors (key2: written by other, read by self)"); + Assert.Equal(Value2, selfValue2); } else { - selfValue2.Should().BeNull("writes to private scopes should not be visible across executors"); + Assert.Null(selfValue2); } otherValue1 = await manager.ReadStateAsync(scopeOtherView, Key1); if (isSharedScope) { - otherValue1.Should().Be(Value1, "published writes should be visible to all executors (key1: written by self, read by other)"); + Assert.Equal(Value1, otherValue1); } else { - otherValue1.Should().BeNull("writes to private scopes should not be visible across executors"); + Assert.Null(otherValue1); } otherValue2 = await manager.ReadStateAsync(scopeOtherView, Key2); - otherValue2.Should().Be(Value2, "published writes should be visible to all executors (key2: written by other, read by other)"); + Assert.Equal(Value2, otherValue2); // Act 4: Clear the value from the self executor's view of the shared scope await manager.ClearStateAsync(scopeSelfView); // Assert 4: The self executor should not see either value immediately, but the other executor should still see both selfValue1 = await manager.ReadStateAsync(scopeSelfView, Key1); - selfValue1.Should().BeNull("clears should be visible immediately to the writing executor"); + Assert.Null(selfValue1); selfValue2 = await manager.ReadStateAsync(scopeSelfView, Key2); - selfValue2.Should().BeNull(isSharedScope ? "clears should be visible immediately to the writing executor" - : "writes to private scopes should not be visible across executors"); + Assert.Null(selfValue2); otherValue1 = await manager.ReadStateAsync(scopeOtherView, Key1); if (isSharedScope) { - otherValue1.Should().Be(Value1, "clears should not be visible to other executors until published (key2: written by self, read by other)"); + Assert.Equal(Value1, otherValue1); } else { - otherValue1.Should().BeNull("writes to private scopes should not be visible across executors"); + Assert.Null(otherValue1); } otherValue2 = await manager.ReadStateAsync(scopeOtherView, Key2); - otherValue2.Should().Be(Value2, isSharedScope ? "clears should not be visible to other executors until published (key2: written by self, read by other)" - : "writes to private scopes should not be visible across executors"); + Assert.Equal(Value2, otherValue2); // Act 5: Publish the updates await manager.PublishUpdatesAsync(tracer: null); // Assert 5: Neither executor should see either value now selfValue1 = await manager.ReadStateAsync(scopeSelfView, Key1); - selfValue1.Should().BeNull("published clears should be visible to all executors"); + Assert.Null(selfValue1); selfValue2 = await manager.ReadStateAsync(scopeSelfView, Key2); - selfValue2.Should().BeNull(isSharedScope ? "published clears should be visible to all executors" - : "writes to private scopes should not be visible across executors"); + Assert.Null(selfValue2); otherValue1 = await manager.ReadStateAsync(scopeOtherView, Key1); - otherValue1.Should().BeNull(isSharedScope ? "published clears should be visible to all executors" - : "writes to private scopes should not be visible across executors"); + Assert.Null(otherValue1); otherValue2 = await manager.ReadStateAsync(scopeOtherView, Key2); if (isSharedScope) { - otherValue2.Should().BeNull("published clears should be visible to all executors"); + Assert.Null(otherValue2); } else { - otherValue2.Should().Be(Value2, "writes to private scopes should not be visible across executors"); + Assert.Equal(Value2, otherValue2); } // Restore the written state of both keys @@ -266,25 +256,23 @@ private static async Task RunValueLifecycleTestAsync(string? scopeName, bool isS // Assert 6: The other executor should not see Key1 immediately, but should still see Key2. The self executor should still see both. selfValue1 = await manager.ReadStateAsync(scopeSelfView, Key1); - selfValue1.Should().Be(Value1, isSharedScope ? "deletes should not be visible to other executors until published (key1: written by other, read by self)" - : "writes to private scopes should not be visible across executors"); + Assert.Equal(Value1, selfValue1); selfValue2 = await manager.ReadStateAsync(scopeSelfView, Key2); if (isSharedScope) { - selfValue2.Should().Be(Value2, "uninvolved keys' state/value should not change after a delete"); + Assert.Equal(Value2, selfValue2); } else { - selfValue2.Should().BeNull("writes to private scopes should not be visible across executors"); + Assert.Null(selfValue2); } otherValue1 = await manager.ReadStateAsync(scopeOtherView, Key1); - otherValue1.Should().BeNull(isSharedScope ? "deletes should be visible immediately to the writing executor" - : "writes to private scopes should not be visible across executors"); + Assert.Null(otherValue1); otherValue2 = await manager.ReadStateAsync(scopeOtherView, Key2); - otherValue2.Should().Be(Value2, "uninvolved keys' state/value should not change after a delete"); + Assert.Equal(Value2, otherValue2); // Act 7: Delete Key2 from the self executor's view of the shared scope await manager.WriteStateAsync(scopeSelfView, Key2, null); @@ -292,20 +280,16 @@ private static async Task RunValueLifecycleTestAsync(string? scopeName, bool isS // Assert 7: The self executor should not see Key2 immediately, but should still see Key1. // The other executor should not see Key1, but should still see Key2. selfValue1 = await manager.ReadStateAsync(scopeSelfView, Key1); - selfValue1.Should().Be(Value1, isSharedScope ? "deletes should not be visible to other executors until published (key1: written by other, read by self)" - : "writes to private scopes should not be visible across executors"); + Assert.Equal(Value1, selfValue1); selfValue2 = await manager.ReadStateAsync(scopeSelfView, Key2); - selfValue2.Should().BeNull(isSharedScope ? "deletes should be visible immediately to the writing executor" - : "writes to private scopes should not be visible across executors"); + Assert.Null(selfValue2); otherValue1 = await manager.ReadStateAsync(scopeOtherView, Key1); - otherValue1.Should().BeNull(isSharedScope ? "deletes should be visible immediately to the writing executor" - : "writes to private scopes should not be visible across executors"); + Assert.Null(otherValue1); otherValue2 = await manager.ReadStateAsync(scopeOtherView, Key2); - otherValue2.Should().Be(Value2, isSharedScope ? "deletes should not be visible to other executors until published (key2: written by self, read by other)" - : "writes to private scopes should not be visible across executors"); + Assert.Equal(Value2, otherValue2); // Act 8: Publish the updates await manager.PublishUpdatesAsync(tracer: null); @@ -314,29 +298,27 @@ private static async Task RunValueLifecycleTestAsync(string? scopeName, bool isS selfValue1 = await manager.ReadStateAsync(scopeSelfView, Key1); if (isSharedScope) { - selfValue1.Should().BeNull("published deletes should be visible to all executors"); + Assert.Null(selfValue1); } else { - selfValue1.Should().Be(Value1, "writes to private scopes should not be visible across executors"); + Assert.Equal(Value1, selfValue1); } selfValue2 = await manager.ReadStateAsync(scopeSelfView, Key2); - selfValue2.Should().BeNull(isSharedScope ? "published deletes should be visible to all executors" - : "writes to private scopes should not be visible across executors"); + Assert.Null(selfValue2); otherValue1 = await manager.ReadStateAsync(scopeOtherView, Key1); - otherValue1.Should().BeNull(isSharedScope ? "published deletes should be visible to all executors" - : "writes to private scopes should not be visible across executors"); + Assert.Null(otherValue1); otherValue2 = await manager.ReadStateAsync(scopeOtherView, Key2); if (isSharedScope) { - otherValue2.Should().BeNull("published deletes should be visible to all executors"); + Assert.Null(otherValue2); } else { - otherValue2.Should().Be(Value2, "writes to private scopes should not be visible across executors"); + Assert.Equal(Value2, otherValue2); } } @@ -369,7 +351,7 @@ private static async Task RunConflictingUpdatesTest_WriteVsWriteAsync(string? sc StateManager manager = new(); ScopeId scopeSelfView = new(SelfExecutorId, scopeName); ScopeId scopeOtherView = new(OtherExecutorId, scopeName); - isSharedScope.Should().Be(scopeSelfView == scopeOtherView); + Assert.Equal(scopeSelfView == scopeOtherView, isSharedScope); // Act 1: Write a conflicting value from the self executor's view of the shared scope // Note that conflicting means update to the same key, not that the values are necessarily different. @@ -381,11 +363,11 @@ private static async Task RunConflictingUpdatesTest_WriteVsWriteAsync(string? sc if (isSharedScope) { - await act.Should().ThrowAsync("conflicting writes to the same key should raise an exception when published"); + await Assert.ThrowsAsync(act); } else { - await act.Should().NotThrowAsync("writes to private scopes should not be visible across executors"); + Assert.Null(await Record.ExceptionAsync(act)); } } @@ -400,7 +382,7 @@ private static async Task RunConflictingUpdatesTest_WriteVsDeleteAsync(string? s StateManager manager = new(); ScopeId scopeSelfView = new(SelfExecutorId, scopeName); ScopeId scopeOtherView = new(OtherExecutorId, scopeName); - isSharedScope.Should().Be(scopeSelfView == scopeOtherView); + Assert.Equal(scopeSelfView == scopeOtherView, isSharedScope); await manager.WriteStateAsync(scopeSelfView, Key1, Value1); await manager.WriteStateAsync(scopeOtherView, Key2, Value2); @@ -413,11 +395,11 @@ private static async Task RunConflictingUpdatesTest_WriteVsDeleteAsync(string? s if (isSharedScope) { - await act.Should().ThrowAsync("conflicting writes (update vs delete) should raise an exception when published"); + await Assert.ThrowsAsync(act); } else { - await act.Should().NotThrowAsync("writes to private scopes should not be visible across executors"); + Assert.Null(await Record.ExceptionAsync(act)); } } @@ -432,7 +414,7 @@ private static async Task RunConflictingUpdatesTest_WriteVsClearAsync(string? sc StateManager manager = new(); ScopeId scopeSelfView = new(SelfExecutorId, scopeName); ScopeId scopeOtherView = new(OtherExecutorId, scopeName); - isSharedScope.Should().Be(scopeSelfView == scopeOtherView); + Assert.Equal(scopeSelfView == scopeOtherView, isSharedScope); await manager.WriteStateAsync(scopeSelfView, Key1, Value1); await manager.WriteStateAsync(scopeOtherView, Key2, Value2); @@ -446,25 +428,25 @@ private static async Task RunConflictingUpdatesTest_WriteVsClearAsync(string? sc // Assert if (isSharedScope) { - await act.Should().ThrowAsync("conflicting writes (update vs clear) should raise an exception when published"); + await Assert.ThrowsAsync(act); } else { - await act.Should().NotThrowAsync("writes to private scopes should not be visible across executors"); + Assert.Null(await Record.ExceptionAsync(act)); } } private static void VerifyIs(PortableValue? candidatePV, TExpectedType value) { - candidatePV.Should().NotBeNull(); - candidatePV.Is(out TExpectedType? candidateValue).Should().BeTrue(); - candidateValue.Should().Be(value); + Assert.NotNull(candidatePV); + Assert.True(candidatePV.Is(out TExpectedType? candidateValue)); + Assert.Equal(value, candidateValue); } private static void VerifyIsNot(PortableValue? candidatePV) { - candidatePV.Should().NotBeNull(); - candidatePV.Is(out TExpectedType? _).Should().BeFalse(); + Assert.NotNull(candidatePV); + Assert.False(candidatePV.Is(out TExpectedType? _)); } [Theory] diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/StreamingAggregatorsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/StreamingAggregatorsTests.cs index 6d605ba313f..e1f9ec433ad 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/StreamingAggregatorsTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/StreamingAggregatorsTests.cs @@ -3,12 +3,14 @@ using System; using System.Collections.Generic; using System.Linq; -using FluentAssertions; namespace Microsoft.Agents.AI.Workflows.UnitTests; public class StreamingAggregatorsTests { + private static readonly int[] s_expectedUnion = [1, 2, 3]; + private static readonly int[] s_expectedAppendedUnion = [1, 2, 3, 4, 5]; + private static TResult? ApplyStreamingAggregator( Func aggregator, IEnumerable inputs, @@ -29,12 +31,11 @@ public void Test_StreamingAggregators_First() Func aggregator = StreamingAggregators.First(); int? runningResult = ApplyStreamingAggregator(aggregator, inputs); - runningResult.Should().Be(1); + Assert.Equal(1, runningResult); + int runningValue = runningResult.GetValueOrDefault(); // Ensure that subsequent inputs do not change the result - ApplyStreamingAggregator(aggregator, inputs.Skip(1), runningResult.Value) - .Should() - .Be(1, "subsequent inputs should not change the result of First aggregator"); + Assert.Equal(1, ApplyStreamingAggregator(aggregator, inputs.Skip(1), runningValue)); } [Fact] @@ -44,12 +45,11 @@ public void Test_StreamingAggregators_First_WithConversion() Func aggregator = StreamingAggregators.First(input => input / 2); int? runningResult = ApplyStreamingAggregator(aggregator, inputs); - runningResult.Should().Be(1); + Assert.Equal(1, runningResult); + int runningValue = runningResult.GetValueOrDefault(); // Ensure that subsequent inputs do not change the result - ApplyStreamingAggregator(aggregator, inputs.Skip(1), runningResult.Value) - .Should() - .Be(1, "subsequent inputs should not change the result of First aggregator with conversion"); + Assert.Equal(1, ApplyStreamingAggregator(aggregator, inputs.Skip(1), runningValue)); } [Fact] @@ -59,12 +59,10 @@ public void Test_StreamingAggregators_Last() Func aggregator = StreamingAggregators.Last(); int? runningResult = ApplyStreamingAggregator(aggregator, inputs); - runningResult.Should().Be(3); + Assert.Equal(3, runningResult); // Ensure that subsequent inputs do change the result - ApplyStreamingAggregator(aggregator, inputs.Take(2), runningResult.Value) - .Should() - .Be(2, "subsequent inputs should change the result of Last aggregator"); + Assert.Equal(2, ApplyStreamingAggregator(aggregator, inputs.Take(2), runningResult.Value)); } [Fact] @@ -74,12 +72,10 @@ public void Test_StreamingAggregators_Last_WithConversion() Func aggregator = StreamingAggregators.Last(input => input / 2); int? runningResult = ApplyStreamingAggregator(aggregator, inputs); - runningResult.Should().Be(3); + Assert.Equal(3, runningResult); // Ensure that subsequent inputs do change the result - ApplyStreamingAggregator(aggregator, inputs.Take(2), runningResult.Value) - .Should() - .Be(2, "subsequent inputs should change the result of Last aggregator"); + Assert.Equal(2, ApplyStreamingAggregator(aggregator, inputs.Take(2), runningResult.Value)); } [Fact] @@ -89,14 +85,12 @@ public void Test_StreamingAggregators_Union() Func?, int, IEnumerable?> aggregator = StreamingAggregators.Union(); IEnumerable? runningResult = ApplyStreamingAggregator(aggregator, inputs); - runningResult.Should().BeEquivalentTo([1, 2, 3], "Union should accumulate all inputs in order"); + Assert.Equivalent(s_expectedUnion, runningResult); // Ensure that subsequent inputs concatenate to the existing results inputs = [4, 5]; - ApplyStreamingAggregator(aggregator, inputs, runningResult) - .Should() - .BeEquivalentTo([1, 2, 3, 4, 5], "Union should accumulate all inputs in order including subsequent inputs"); + Assert.Equivalent(s_expectedAppendedUnion, ApplyStreamingAggregator(aggregator, inputs, runningResult)); } [Fact] @@ -106,14 +100,10 @@ public void Test_StreamingAggregators_Union_WithConversion() Func?, int, IEnumerable?> aggregator = StreamingAggregators.Union(input => input / 2); IEnumerable? runningResult = ApplyStreamingAggregator(aggregator, inputs); - runningResult.Should().BeEquivalentTo([1, 2, 3], - "Union with conversion should accumulate all converted inputs in order"); + Assert.Equivalent(s_expectedUnion, runningResult); // Ensure that subsequent inputs concatenate to the existing results inputs = [8, 10]; - ApplyStreamingAggregator(aggregator, inputs, runningResult) - .Should() - .BeEquivalentTo([1, 2, 3, 4, 5], - "Union with conversion should accumulate all converted inputs in order including subsequent inputs"); + Assert.Equivalent(s_expectedAppendedUnion, ApplyStreamingAggregator(aggregator, inputs, runningResult)); } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestProgressLedgerState.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestProgressLedgerState.cs index fc7e9ded2de..573525b2551 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestProgressLedgerState.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestProgressLedgerState.cs @@ -2,7 +2,6 @@ using System.Text.Json; using System.Text.Json.Serialization; -using FluentAssertions; namespace Microsoft.Agents.AI.Workflows.UnitTests; @@ -45,30 +44,30 @@ public static bool TryGetCustom2(MagenticProgressLedger state, out string? resul public void Validate(MagenticProgressLedger state) { - state.IsRequestSatisfied.Should().Be(this.is_request_satisfied!.answer!.Value); - state.IsInLoop.Should().Be(this.is_in_loop!.answer!.Value); - state.IsProgressBeingMade.Should().Be(this.is_progress_being_made!.answer!.Value); - state.InstructionOrQuestion.Should().Be(this.instruction_or_question!.answer); - state.NextSpeaker.Should().Be(this.next_speaker!.answer); + Assert.Equal(this.is_request_satisfied!.answer!.Value, state.IsRequestSatisfied); + Assert.Equal(this.is_in_loop!.answer!.Value, state.IsInLoop); + Assert.Equal(this.is_progress_being_made!.answer!.Value, state.IsProgressBeingMade); + Assert.Equal(this.instruction_or_question!.answer, state.InstructionOrQuestion); + Assert.Equal(this.next_speaker!.answer, state.NextSpeaker); if (this.custom1 != null) { - TryGetCustom1(state, out bool custom1Value).Should().BeTrue(); - custom1Value.Should().Be(this.custom1.answer!.Value); + Assert.True(TryGetCustom1(state, out bool custom1Value)); + Assert.Equal(this.custom1.answer!.Value, custom1Value); } else { - TryGetCustom1(state, out _).Should().BeFalse(); + Assert.False(TryGetCustom1(state, out _)); } if (this.custom2 != null) { - TryGetCustom2(state, out string? custom2Value).Should().BeTrue(); - custom2Value.Should().Be(this.custom2.answer); + Assert.True(TryGetCustom2(state, out string? custom2Value)); + Assert.Equal(this.custom2.answer, custom2Value); } else { - TryGetCustom2(state, out _).Should().BeFalse(); + Assert.False(TryGetCustom2(state, out _)); } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestRequestAgent.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestRequestAgent.cs index 32b082ad6d0..a3c25e1ff19 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestRequestAgent.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestRequestAgent.cs @@ -7,7 +7,6 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.Extensions.AI; namespace Microsoft.Agents.AI.Workflows.UnitTests; @@ -160,7 +159,7 @@ public void ProcessResponse(FunctionResultContent response, TestRequestAgentSess { if (session.UnservicedRequests.TryGetValue(response.CallId, out FunctionCallContent? request)) { - response.Result.As().Should().Be(request); + Assert.Equal(request, Assert.IsType(response.Result)); session.ServicedRequests.Add(response.CallId); session.UnservicedRequests.Remove(response.CallId); } @@ -200,8 +199,8 @@ public void ProcessResponse(ToolApprovalResponseContent response, TestRequestAge { if (session.UnservicedRequests.TryGetValue(response.RequestId, out ToolApprovalRequestContent? request)) { - response.Approved.Should().BeTrue(); - ((FunctionCallContent)response.ToolCall).Should().Be((FunctionCallContent)request.ToolCall); + Assert.True(response.Approved); + Assert.Equal((FunctionCallContent)request.ToolCall, ((FunctionCallContent)response.ToolCall)); session.ServicedRequests.Add(response.RequestId); session.UnservicedRequests.Remove(response.RequestId); } @@ -255,14 +254,15 @@ private IEnumerable ValidateUnpairedRequests(IEn where TRequest : AIContent where TResponse : AIContent { - this.LastSession.Should().NotBeNull(); + Assert.NotNull(this.LastSession); TestRequestAgentSession traSession = ConvertSession(this.LastSession); - requests.Should().HaveCount(traSession.UnservicedRequests.Count); - foreach (TRequest request in requests) + List requestList = requests.ToList(); + Assert.Equal(traSession.UnservicedRequests.Count, requestList.Count); + foreach (TRequest request in requestList) { string requestId = RetrieveId(request); - traSession.UnservicedRequests.Should().ContainKey(requestId); + Assert.Contains(requestId, traSession.UnservicedRequests); yield return strategy.CreatePairedResponse(request); } } @@ -310,7 +310,7 @@ internal IEnumerable ValidateUnpairedRequests(List(ExternalRequest request) { - request.TryGetDataAs(out TRequest? content).Should().BeTrue(); + Assert.True(request.TryGetDataAs(out TRequest? content)); return content!; } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TypeIdVersionToleranceTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TypeIdVersionToleranceTests.cs index ef3379dca5d..e7201fc1e3a 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TypeIdVersionToleranceTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TypeIdVersionToleranceTests.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using FluentAssertions; using Microsoft.Agents.AI.Workflows.Checkpointing; using Microsoft.Extensions.AI; @@ -30,8 +29,8 @@ public void Test_IsMatch_RoundTripsRealType() { TypeId id = new(typeof(Probe)); - id.IsMatch(typeof(Probe)).Should().BeTrue(); - id.IsMatch().Should().BeTrue(); + Assert.True(id.IsMatch(typeof(Probe))); + Assert.True(id.IsMatch()); } [Fact] @@ -40,7 +39,7 @@ public void Test_IsMatch_IgnoresAssemblyVersion() string assemblyName = $"{ProbeSimpleAssemblyName}, Version=99.0.0.0, Culture=neutral, PublicKeyToken=null"; TypeId id = new(assemblyName, ProbeTypeFullName); - id.IsMatch(typeof(Probe)).Should().BeTrue("version differences in AssemblyName must not affect matching"); + Assert.True(id.IsMatch(typeof(Probe))); } [Fact] @@ -49,7 +48,7 @@ public void Test_IsMatch_IgnoresCultureAndPublicKeyToken() string assemblyName = $"{ProbeSimpleAssemblyName}, Version=99.0.0.0, Culture=en-US, PublicKeyToken=abcdef0123456789"; TypeId id = new(assemblyName, ProbeTypeFullName); - id.IsMatch(typeof(Probe)).Should().BeTrue(); + Assert.True(id.IsMatch(typeof(Probe))); } [Fact] @@ -57,7 +56,7 @@ public void Test_IsMatch_AcceptsSimpleAssemblyNameOnly() { TypeId id = new(ProbeSimpleAssemblyName, ProbeTypeFullName); - id.IsMatch(typeof(Probe)).Should().BeTrue(); + Assert.True(id.IsMatch(typeof(Probe))); } [Fact] @@ -67,7 +66,7 @@ public void Test_IsMatch_RejectsDifferentSimpleAssemblyName() assemblyName: "Some.Completely.Different.Assembly, Version=1.0.0.0", typeName: ProbeTypeFullName); - id.IsMatch(typeof(Probe)).Should().BeFalse("different simple assembly names must not match"); + Assert.False(id.IsMatch(typeof(Probe))); } [Fact] @@ -77,7 +76,7 @@ public void Test_IsMatch_RejectsDifferentTypeName() assemblyName: $"{ProbeSimpleAssemblyName}, Version=99.0.0.0", typeName: "Some.Other.Namespace.Probe"); - id.IsMatch(typeof(Probe)).Should().BeFalse("different type names must not match"); + Assert.False(id.IsMatch(typeof(Probe))); } [Fact] @@ -87,7 +86,7 @@ public void Test_IsMatch_ToleratesMalformedAssemblyName() assemblyName: $"{ProbeSimpleAssemblyName}, Version=not-a-version, Culture=??, PublicKeyToken=???", typeName: ProbeTypeFullName); - id.IsMatch(typeof(Probe)).Should().BeTrue("the substring fallback recovers the simple name when AssemblyName parsing fails"); + Assert.True(id.IsMatch(typeof(Probe))); } [Fact] @@ -97,7 +96,7 @@ public void Test_IsMatchPolymorphic_IgnoresAssemblyVersion() assemblyName: $"{typeof(object).Assembly.GetName().Name}, Version=99.0.0.0", typeName: typeof(object).FullName!); - id.IsMatchPolymorphic(typeof(Probe)).Should().BeTrue("IsMatchPolymorphic uses the same comparison rules as IsMatch"); + Assert.True(id.IsMatchPolymorphic(typeof(Probe))); } [Fact] @@ -106,9 +105,9 @@ public void Test_Equals_IgnoresAssemblyVersion() TypeId v1 = new($"{ProbeSimpleAssemblyName}, Version=1.0.0.0", ProbeTypeFullName); TypeId v2 = new($"{ProbeSimpleAssemblyName}, Version=2.0.0.0", ProbeTypeFullName); - v1.Equals(v2).Should().BeTrue(); - (v1 == v2).Should().BeTrue(); - v1.GetHashCode().Should().Be(v2.GetHashCode()); + Assert.True(v1.Equals(v2)); + Assert.True((v1 == v2)); + Assert.Equal(v2.GetHashCode(), v1.GetHashCode()); } [Fact] @@ -117,7 +116,7 @@ public void Test_Equals_RejectsDifferentSimpleAssemblyName() TypeId a = new($"{ProbeSimpleAssemblyName}, Version=1.0.0.0", ProbeTypeFullName); TypeId b = new("Some.Other.Assembly, Version=1.0.0.0", ProbeTypeFullName); - a.Equals(b).Should().BeFalse(); + Assert.False(a.Equals(b)); } [Fact] @@ -126,7 +125,7 @@ public void Test_Equals_RejectsDifferentTypeName() TypeId a = new($"{ProbeSimpleAssemblyName}, Version=1.0.0.0", ProbeTypeFullName); TypeId b = new($"{ProbeSimpleAssemblyName}, Version=1.0.0.0", "Some.Other.Type"); - a.Equals(b).Should().BeFalse(); + Assert.False(a.Equals(b)); } [Fact] @@ -138,11 +137,11 @@ public void Test_Dictionary_LookupAcrossVersions() typeName: ProbeTypeFullName); Dictionary map = new() { [live] = "value" }; - map.TryGetValue(mutated, out string? value).Should().BeTrue(); - value.Should().Be("value"); + Assert.True(map.TryGetValue(mutated, out string? value)); + Assert.Equal("value", value); HashSet set = new() { live }; - set.Contains(mutated).Should().BeTrue(); + Assert.Contains(mutated, set); } [Fact] @@ -151,8 +150,8 @@ public void Test_Equals_TreatsIdenticalStringsAsEqual() TypeId a = new(typeof(Probe)); TypeId b = new(typeof(Probe)); - a.Equals(b).Should().BeTrue(); - a.GetHashCode().Should().Be(b.GetHashCode()); + Assert.True(a.Equals(b)); + Assert.Equal(b.GetHashCode(), a.GetHashCode()); } [Fact] @@ -160,7 +159,7 @@ public void Test_NormalizeTypeName_ReturnsInputWhenNoAssemblyQualifier() { const string TypeName = "Microsoft.Agents.AI.Workflows.Checkpointing.TypeId"; - TypeId.NormalizeTypeName(TypeName).Should().BeSameAs(TypeName); + Assert.Same(TypeName, TypeId.NormalizeTypeName(TypeName)); } [Fact] @@ -169,7 +168,7 @@ public void Test_NormalizeTypeName_StripsVersionCultureAndPublicKeyTokenTriplets const string TypeName = "System.Collections.Generic.List`1[[Some.Type, Some.Asm, Version=1.2.3.4, Culture=neutral, PublicKeyToken=abcdef0123456789]]"; const string Expected = "System.Collections.Generic.List`1[[Some.Type, Some.Asm]]"; - TypeId.NormalizeTypeName(TypeName).Should().Be(Expected); + Assert.Equal(Expected, TypeId.NormalizeTypeName(TypeName)); } [Fact] @@ -178,7 +177,7 @@ public void Test_NormalizeTypeName_StripsTripletsFromNestedGenericArguments() const string TypeName = "System.Collections.Generic.Dictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Collections.Generic.List`1[[Some.Type, Some.Asm, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"; const string Expected = "System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[System.Collections.Generic.List`1[[Some.Type, Some.Asm]], mscorlib]]"; - TypeId.NormalizeTypeName(TypeName).Should().Be(Expected); + Assert.Equal(Expected, TypeId.NormalizeTypeName(TypeName)); } [Fact] @@ -194,7 +193,7 @@ public void Test_IsMatch_IgnoresVersionInGenericArguments() TypeId id = new(simpleAssemblyName, mutatedTypeName); - id.IsMatch(live).Should().BeTrue("version differences inside generic argument names must not affect matching"); + Assert.True(id.IsMatch(live)); } [Fact] @@ -208,8 +207,8 @@ public void Test_Equals_IgnoresVersionInGenericArguments() string mutatedTypeName = $"System.Collections.Generic.List`1[[Microsoft.Extensions.AI.ChatMessage, {innerArgSimpleName}, Version=99.0.0.0, Culture=neutral, PublicKeyToken=null]]"; TypeId fromMutated = new(simpleAssemblyName, mutatedTypeName); - fromLive.Equals(fromMutated).Should().BeTrue(); - fromLive.GetHashCode().Should().Be(fromMutated.GetHashCode()); + Assert.True(fromLive.Equals(fromMutated)); + Assert.Equal(fromMutated.GetHashCode(), fromLive.GetHashCode()); } [Fact] @@ -224,7 +223,7 @@ public void Test_Dictionary_LookupAcrossGenericArgumentVersions() TypeId fromMutated = new(simpleAssemblyName, mutatedTypeName); Dictionary map = new() { [fromLive] = "value" }; - map.TryGetValue(fromMutated, out string? value).Should().BeTrue(); - value.Should().Be("value"); + Assert.True(map.TryGetValue(fromMutated, out string? value)); + Assert.Equal("value", value); } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowAgentCheckpointIdentityTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowAgentCheckpointIdentityTests.cs index 92cbbbeb333..39263fef6ea 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowAgentCheckpointIdentityTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowAgentCheckpointIdentityTests.cs @@ -8,7 +8,6 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.Extensions.AI; namespace Microsoft.Agents.AI.Workflows.UnitTests; @@ -37,9 +36,7 @@ public async Task WorkflowAgentSession_WithStableInnerAgentIds_ResumesAcrossReco // Act: complete a first turn (triage hands off to the specialist), then serialize the session. AgentResponse firstResponse = await firstGeneration.RunAsync("Please help me.", session); - firstResponse.Text.Should().Be( - $"{SpecialistReply}:turn:1", - "the first turn should route triage -> specialist, and the specialist observes a single user turn"); + Assert.Equal($"{SpecialistReply}:turn:1", firstResponse.Text); JsonElement serialized = await firstGeneration.SerializeSessionAsync(session); @@ -53,9 +50,7 @@ public async Task WorkflowAgentSession_WithStableInnerAgentIds_ResumesAcrossReco // Assert: the specialist observes both user turns, which is only possible if the checkpointed conversation was // restored. A fresh (non-resumed) session would restart the count at turn:1, so this distinguishes a genuine // resume from a compatible-but-empty restart. - secondResponse.Text.Should().Be( - $"{SpecialistReply}:turn:2", - "stable inner agent ids keep the executor identities compatible and the reconstructed workflow resumes from the checkpoint"); + Assert.Equal($"{SpecialistReply}:turn:2", secondResponse.Text); } [Fact] @@ -67,7 +62,7 @@ public async Task WorkflowAgentSession_WithoutStableInnerAgentIds_FailsAcrossRec // Complete a first turn and serialize the session; a completed handoff turn captures a checkpoint. AgentResponse firstResponse = await firstGeneration.RunAsync("Please help me.", session); - firstResponse.Text.Should().Contain(SpecialistReply, "the first turn should route triage -> specialist"); + Assert.Contains(SpecialistReply, firstResponse.Text); JsonElement serialized = await firstGeneration.SerializeSessionAsync(session); @@ -82,8 +77,8 @@ public async Task WorkflowAgentSession_WithoutStableInnerAgentIds_FailsAcrossRec Func resumeAndRun = () => secondGeneration.RunAsync("Anything else?", resumedSession); // Assert: the second run throws because the reconstructed executor ids no longer match the checkpoint. - await resumeAndRun.Should().ThrowAsync() - .WithMessage("The specified checkpoint is not compatible with the workflow associated with this runner."); + InvalidDataException exception = await Assert.ThrowsAsync(resumeAndRun); + Assert.Equal("The specified checkpoint is not compatible with the workflow associated with this runner.", exception.Message); } [Fact] @@ -94,7 +89,7 @@ public async Task WorkflowAgentSession_WithStableIdsButChangedInnerNames_FailsAc AgentSession session = await firstGeneration.CreateSessionAsync(); AgentResponse firstResponse = await firstGeneration.RunAsync("Please help me.", session); - firstResponse.Text.Should().Contain(SpecialistReply, "the first turn should route triage -> specialist"); + Assert.Contains(SpecialistReply, firstResponse.Text); JsonElement serialized = await firstGeneration.SerializeSessionAsync(session); @@ -108,8 +103,8 @@ public async Task WorkflowAgentSession_WithStableIdsButChangedInnerNames_FailsAc Func resumeAndRun = () => secondGeneration.RunAsync("Anything else?", resumedSession); // Assert: changing a set name invalidates the executor identity even though the id is stable. - await resumeAndRun.Should().ThrowAsync() - .WithMessage("The specified checkpoint is not compatible with the workflow associated with this runner."); + InvalidDataException exception = await Assert.ThrowsAsync(resumeAndRun); + Assert.Equal("The specified checkpoint is not compatible with the workflow associated with this runner.", exception.Message); } /// diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowBuilderTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowBuilderTests.cs index 88664d4d404..9ef21c3424c 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowBuilderTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowBuilderTests.cs @@ -5,7 +5,6 @@ using System.Globalization; using System.Numerics; using System.Reflection; -using FluentAssertions; namespace Microsoft.Agents.AI.Workflows.UnitTests; @@ -35,7 +34,7 @@ public void Test_Validation_FailsWhenUnboundExecutors() .Build(); }; - act.Should().Throw(); + Assert.Throws(act); } [Fact] @@ -48,7 +47,7 @@ public void Test_Validation_FailsWhenUnreachableExecutors() .AddEdge(new NoOpExecutor("unreachable"), new NoOpExecutor("also-unreachable")) .Build(); }; - act.Should().Throw(); + Assert.Throws(act); } [Fact] @@ -60,14 +59,14 @@ public void Test_Validation_AddEdgesOutOfOrderDoesNotImpactReachability() .AddEdge("start", "not-unreachable") .Build(); - workflow.StartExecutorId.Should().Be("start"); + Assert.Equal("start", workflow.StartExecutorId); - workflow.ExecutorBindings.Should().HaveCount(3); - workflow.ExecutorBindings.Should().ContainKey("start"); - workflow.ExecutorBindings.Should().ContainKey("not-unreachable"); - workflow.ExecutorBindings.Should().ContainKey("also-not-unreachable"); + Assert.Equal(3, workflow.ExecutorBindings.Count); + Assert.Contains("start", workflow.ExecutorBindings); + Assert.Contains("not-unreachable", workflow.ExecutorBindings); + Assert.Contains("also-not-unreachable", workflow.ExecutorBindings); - workflow.ExecutorBindings.Values.Should().AllSatisfy(binding => binding.ExecutorType.Should().Be()); + Assert.All(workflow.ExecutorBindings.Values, binding => Assert.Equal(typeof(NoOpExecutor), binding.ExecutorType)); } [Fact] @@ -77,11 +76,11 @@ public void Test_LateBinding_Executor() .BindExecutor(new NoOpExecutor("start")) .Build(); - workflow.StartExecutorId.Should().Be("start"); + Assert.Equal("start", workflow.StartExecutorId); - workflow.ExecutorBindings.Should().HaveCount(1); - workflow.ExecutorBindings.Should().ContainKey("start"); - workflow.ExecutorBindings["start"].ExecutorType.Should().Be(); + Assert.Single(workflow.ExecutorBindings); + Assert.Contains("start", workflow.ExecutorBindings!); + Assert.Equal(typeof(NoOpExecutor), workflow.ExecutorBindings["start"].ExecutorType); } [Fact] @@ -92,11 +91,11 @@ public void Test_LateImplicitBinding_Executor() .AddEdge(start, start) .Build(); - workflow.StartExecutorId.Should().Be("start"); + Assert.Equal("start", workflow.StartExecutorId); - workflow.ExecutorBindings.Should().HaveCount(1); - workflow.ExecutorBindings.Should().ContainKey("start"); - workflow.ExecutorBindings["start"].ExecutorType.Should().Be(); + Assert.Single(workflow.ExecutorBindings); + Assert.Contains("start", workflow.ExecutorBindings!); + Assert.Equal(typeof(NoOpExecutor), workflow.ExecutorBindings["start"].ExecutorType); } [Fact] @@ -112,7 +111,7 @@ public void Test_RebindToDifferent_Disallowed() .Build(); }; - act.Should().Throw(); + Assert.Throws(act); } [Fact] @@ -124,11 +123,11 @@ public void Test_RebindToSameish_Allowed() .AddEdge(executor1, executor1) .Build(); - workflow.StartExecutorId.Should().Be("start"); + Assert.Equal("start", workflow.StartExecutorId); - workflow.ExecutorBindings.Should().HaveCount(1); - workflow.ExecutorBindings.Should().ContainKey("start"); - workflow.ExecutorBindings["start"].ExecutorType.Should().Be(); + Assert.Single(workflow.ExecutorBindings); + Assert.Contains("start", workflow.ExecutorBindings!); + Assert.Equal(typeof(NoOpExecutor), workflow.ExecutorBindings["start"].ExecutorType); } [Fact] @@ -141,16 +140,16 @@ public void Test_Workflow_NameAndDescription() .BindExecutor(new NoOpExecutor("start")) .Build(); - workflow1.Name.Should().Be("Test Pipeline"); - workflow1.Description.Should().Be("Test workflow description"); + Assert.Equal("Test Pipeline", workflow1.Name); + Assert.Equal("Test workflow description", workflow1.Description); // Test without (defaults to null) Workflow workflow2 = new WorkflowBuilder("start2") .BindExecutor(new NoOpExecutor("start2")) .Build(); - workflow2.Name.Should().BeNull(); - workflow2.Description.Should().BeNull(); + Assert.Null(workflow2.Name); + Assert.Null(workflow2.Description); // Test with only name (no description) Workflow workflow3 = new WorkflowBuilder("start3") @@ -158,8 +157,8 @@ public void Test_Workflow_NameAndDescription() .BindExecutor(new NoOpExecutor("start3")) .Build(); - workflow3.Name.Should().Be("Named Only"); - workflow3.Description.Should().BeNull(); + Assert.Equal("Named Only", workflow3.Name); + Assert.Null(workflow3.Description); } [Fact] @@ -176,14 +175,14 @@ public void ForwardMessage_WithSingleTarget_CreatesDirectEdge() // Assert Edge edge = GetSingleEdge(workflow, source.Id); - edge.Kind.Should().Be(EdgeKind.Direct); - edge.DirectEdgeData.Should().NotBeNull(); - edge.DirectEdgeData!.SourceId.Should().Be(source.Id); - edge.DirectEdgeData!.SinkId.Should().Be(target.Id); - edge.DirectEdgeData.Condition.Should().NotBeNull(); - edge.DirectEdgeData.Condition!("message").Should().BeTrue(); - edge.DirectEdgeData.Condition!(42).Should().BeFalse(); - edge.DirectEdgeData.Condition!(null).Should().BeFalse(); + Assert.Equal(EdgeKind.Direct, edge.Kind); + Assert.NotNull(edge.DirectEdgeData); + Assert.Equal(source.Id, edge.DirectEdgeData!.SourceId); + Assert.Equal(target.Id, edge.DirectEdgeData!.SinkId); + Assert.NotNull(edge.DirectEdgeData.Condition); + Assert.True(edge.DirectEdgeData.Condition!("message")); + Assert.False(edge.DirectEdgeData.Condition!(42)); + Assert.False(edge.DirectEdgeData.Condition!(null)); } [Fact] @@ -201,14 +200,14 @@ public void ForwardMessage_WithMultipleTargets_CreatesFanOutEdge() // Assert Edge edge = GetSingleEdge(workflow, source.Id); - edge.Kind.Should().Be(EdgeKind.FanOut); - edge.FanOutEdgeData.Should().NotBeNull(); - edge.FanOutEdgeData!.SourceId.Should().Be(source.Id); - edge.FanOutEdgeData!.SinkIds.Should().Equal([target1.Id, target2.Id]); - edge.FanOutEdgeData.EdgeAssigner.Should().NotBeNull(); - edge.FanOutEdgeData.EdgeAssigner!("match", 2).Should().Equal([0, 1]); - edge.FanOutEdgeData.EdgeAssigner!("other", 2).Should().BeEmpty(); - edge.FanOutEdgeData.EdgeAssigner!(42, 2).Should().BeEmpty(); + Assert.Equal(EdgeKind.FanOut, edge.Kind); + Assert.NotNull(edge.FanOutEdgeData); + Assert.Equal(source.Id, edge.FanOutEdgeData!.SourceId); + Assert.Equal([target1.Id, target2.Id], edge.FanOutEdgeData!.SinkIds); + Assert.NotNull(edge.FanOutEdgeData.EdgeAssigner); + Assert.Equal([0, 1], edge.FanOutEdgeData.EdgeAssigner!("match", 2)); + Assert.Empty(edge.FanOutEdgeData.EdgeAssigner!("other", 2) ?? []); + Assert.Empty(edge.FanOutEdgeData.EdgeAssigner!(42, 2) ?? []); } [Fact] @@ -225,14 +224,14 @@ public void ForwardExcept_WithSingleTarget_CreatesDirectEdge() // Assert Edge edge = GetSingleEdge(workflow, source.Id); - edge.Kind.Should().Be(EdgeKind.Direct); - edge.DirectEdgeData.Should().NotBeNull(); - edge.DirectEdgeData!.SourceId.Should().Be(source.Id); - edge.DirectEdgeData!.SinkId.Should().Be(target.Id); - edge.DirectEdgeData.Condition.Should().NotBeNull(); - edge.DirectEdgeData.Condition!("message").Should().BeFalse(); - edge.DirectEdgeData.Condition!(42).Should().BeTrue(); - edge.DirectEdgeData.Condition!(null).Should().BeTrue(); + Assert.Equal(EdgeKind.Direct, edge.Kind); + Assert.NotNull(edge.DirectEdgeData); + Assert.Equal(source.Id, edge.DirectEdgeData!.SourceId); + Assert.Equal(target.Id, edge.DirectEdgeData!.SinkId); + Assert.NotNull(edge.DirectEdgeData.Condition); + Assert.False(edge.DirectEdgeData.Condition!("message")); + Assert.True(edge.DirectEdgeData.Condition!(42)); + Assert.True(edge.DirectEdgeData.Condition!(null)); } [Fact] @@ -250,13 +249,13 @@ public void ForwardExcept_WithMultipleTargets_CreatesFanOutEdge() // Assert Edge edge = GetSingleEdge(workflow, source.Id); - edge.Kind.Should().Be(EdgeKind.FanOut); - edge.FanOutEdgeData.Should().NotBeNull(); - edge.FanOutEdgeData!.SourceId.Should().Be(source.Id); - edge.FanOutEdgeData!.SinkIds.Should().Equal([target1.Id, target2.Id]); - edge.FanOutEdgeData.EdgeAssigner.Should().NotBeNull(); - edge.FanOutEdgeData.EdgeAssigner!(42, 2).Should().Equal([0, 1]); - edge.FanOutEdgeData.EdgeAssigner!("message", 2).Should().BeEmpty(); + Assert.Equal(EdgeKind.FanOut, edge.Kind); + Assert.NotNull(edge.FanOutEdgeData); + Assert.Equal(source.Id, edge.FanOutEdgeData!.SourceId); + Assert.Equal([target1.Id, target2.Id], edge.FanOutEdgeData!.SinkIds); + Assert.NotNull(edge.FanOutEdgeData.EdgeAssigner); + Assert.Equal([0, 1], edge.FanOutEdgeData.EdgeAssigner!(42, 2)); + Assert.Empty(edge.FanOutEdgeData.EdgeAssigner!("message", 2) ?? []); } [Fact] @@ -274,14 +273,14 @@ public void AddChain_CreatesSequentialDirectEdges() // Assert Edge firstEdge = GetSingleEdge(workflow, source.Id); - firstEdge.Kind.Should().Be(EdgeKind.Direct); - firstEdge.DirectEdgeData!.SourceId.Should().Be(source.Id); - firstEdge.DirectEdgeData.SinkId.Should().Be(middle.Id); + Assert.Equal(EdgeKind.Direct, firstEdge.Kind); + Assert.Equal(source.Id, firstEdge.DirectEdgeData!.SourceId); + Assert.Equal(middle.Id, firstEdge.DirectEdgeData.SinkId); Edge secondEdge = GetSingleEdge(workflow, middle.Id); - secondEdge.Kind.Should().Be(EdgeKind.Direct); - secondEdge.DirectEdgeData!.SourceId.Should().Be(middle.Id); - secondEdge.DirectEdgeData.SinkId.Should().Be(end.Id); + Assert.Equal(EdgeKind.Direct, secondEdge.Kind); + Assert.Equal(middle.Id, secondEdge.DirectEdgeData!.SourceId); + Assert.Equal(end.Id, secondEdge.DirectEdgeData.SinkId); } [Fact] @@ -296,8 +295,7 @@ public void AddChain_WhenExecutorRepeats_Throws() .AddChain(source, [middle, source]); // Assert - act.Should().Throw() - .WithParameterName("executors"); + Assert.Equal("executors", (Assert.Throws(act)).ParamName); } [Fact] @@ -313,20 +311,20 @@ public void AddExternalCall_CreatesRequestPortAndRoundTripEdges() .Build(); // Assert - workflow.Ports.Should().ContainKey(PortId); - workflow.Ports[PortId].Request.Should().Be(); - workflow.Ports[PortId].Response.Should().Be(); - workflow.ExecutorBindings.Should().ContainKey(PortId); + Assert.Contains(PortId, workflow.Ports); + Assert.Equal(typeof(string), workflow.Ports[PortId].Request); + Assert.Equal(typeof(int), workflow.Ports[PortId].Response); + Assert.Contains(PortId, workflow.ExecutorBindings); Edge requestEdge = GetSingleEdge(workflow, source.Id); - requestEdge.Kind.Should().Be(EdgeKind.Direct); - requestEdge.DirectEdgeData!.SourceId.Should().Be(source.Id); - requestEdge.DirectEdgeData.SinkId.Should().Be(PortId); + Assert.Equal(EdgeKind.Direct, requestEdge.Kind); + Assert.Equal(source.Id, requestEdge.DirectEdgeData!.SourceId); + Assert.Equal(PortId, requestEdge.DirectEdgeData.SinkId); Edge responseEdge = GetSingleEdge(workflow, PortId); - responseEdge.Kind.Should().Be(EdgeKind.Direct); - responseEdge.DirectEdgeData!.SourceId.Should().Be(PortId); - responseEdge.DirectEdgeData.SinkId.Should().Be(source.Id); + Assert.Equal(EdgeKind.Direct, responseEdge.Kind); + Assert.Equal(PortId, responseEdge.DirectEdgeData!.SourceId); + Assert.Equal(source.Id, responseEdge.DirectEdgeData.SinkId); } [Fact] @@ -348,14 +346,14 @@ public void AddSwitch_CreatesFanOutEdgeWithCasesAndDefault() // Assert Edge edge = GetSingleEdge(workflow, source.Id); - edge.Kind.Should().Be(EdgeKind.FanOut); - edge.FanOutEdgeData.Should().NotBeNull(); - edge.FanOutEdgeData!.SourceId.Should().Be(source.Id); - edge.FanOutEdgeData!.SinkIds.Should().Equal([stringTarget.Id, intTarget.Id, defaultTarget.Id]); - edge.FanOutEdgeData.EdgeAssigner.Should().NotBeNull(); - edge.FanOutEdgeData.EdgeAssigner!("match", 3).Should().Equal([0]); - edge.FanOutEdgeData.EdgeAssigner!(2, 3).Should().Equal([1]); - edge.FanOutEdgeData.EdgeAssigner!("other", 3).Should().Equal([2]); + Assert.Equal(EdgeKind.FanOut, edge.Kind); + Assert.NotNull(edge.FanOutEdgeData); + Assert.Equal(source.Id, edge.FanOutEdgeData!.SourceId); + Assert.Equal([stringTarget.Id, intTarget.Id, defaultTarget.Id], edge.FanOutEdgeData!.SinkIds); + Assert.NotNull(edge.FanOutEdgeData.EdgeAssigner); + Assert.Equal([0], edge.FanOutEdgeData.EdgeAssigner!("match", 3)); + Assert.Equal([1], edge.FanOutEdgeData.EdgeAssigner!(2, 3)); + Assert.Equal([2], edge.FanOutEdgeData.EdgeAssigner!("other", 3)); } [Fact] @@ -457,7 +455,7 @@ public void SwitchBuilder_InvalidArguments_Throw() /// Gets the only edge emitted by the specified workflow source. /// private static Edge GetSingleEdge(Workflow workflow, string sourceId) - => workflow.Edges[sourceId].Should().ContainSingle().Subject; + => Assert.Single(workflow.Edges[sourceId]); // --- Tag-aware WithOutputFrom / WithIntermediateOutputFrom tests --- @@ -471,8 +469,8 @@ public void Test_WithOutputFrom_RegistersWithEmptyTagSet() .WithOutputFrom(b) .Build(); - workflow.OutputExecutors.Should().ContainKey("b"); - workflow.OutputExecutors["b"].Should().BeEmpty("regular outputs are untagged"); + Assert.Contains("b", workflow.OutputExecutors); + Assert.Empty(workflow.OutputExecutors["b"] ?? []); } [Fact] @@ -485,7 +483,7 @@ public void Test_WithIntermediateOutputFrom_AddsIntermediateTag() .WithIntermediateOutputFrom([b]) .Build(); - workflow.OutputExecutors["b"].Should().BeEquivalentTo([OutputTag.Intermediate]); + Assert.Equivalent(new[] { OutputTag.Intermediate }, workflow.OutputExecutors["b"]); } [Fact] @@ -500,9 +498,9 @@ public void Test_WithOutputFrom_MultipleExecutorsAllUntagged() .WithOutputFrom(b, c) .Build(); - workflow.OutputExecutors.Should().HaveCount(2); - workflow.OutputExecutors["b"].Should().BeEmpty(); - workflow.OutputExecutors["c"].Should().BeEmpty(); + Assert.Equal(2, workflow.OutputExecutors.Count); + Assert.Empty(workflow.OutputExecutors["b"] ?? []); + Assert.Empty(workflow.OutputExecutors["c"] ?? []); } [Fact] @@ -517,7 +515,7 @@ public void Test_WithOutputFrom_ThenIntermediate_AccumulatesTags() .Build(); // WithOutputFrom doesn't add a tag; WithIntermediateOutputFrom adds Intermediate. - workflow.OutputExecutors["b"].Should().BeEquivalentTo([OutputTag.Intermediate]); + Assert.Equivalent(new[] { OutputTag.Intermediate }, workflow.OutputExecutors["b"]); } [Fact] @@ -531,7 +529,7 @@ public void Test_WithIntermediateOutputFrom_RepeatedDedupes() .WithIntermediateOutputFrom([b]) .Build(); - workflow.OutputExecutors["b"].Should().BeEquivalentTo([OutputTag.Intermediate]); + Assert.Equivalent(new[] { OutputTag.Intermediate }, workflow.OutputExecutors["b"]); } [Fact] @@ -546,8 +544,8 @@ public void Test_WithIntermediateOutputFrom_OnlyRegistersWithoutPriorWithOutputF .WithIntermediateOutputFrom([b]) .Build(); - workflow.OutputExecutors.Should().ContainKey("b"); - workflow.OutputExecutors["b"].Should().BeEquivalentTo([OutputTag.Intermediate]); + Assert.Contains("b", workflow.OutputExecutors); + Assert.Equivalent(new[] { OutputTag.Intermediate }, workflow.OutputExecutors["b"]); } [Fact] @@ -563,8 +561,8 @@ public void Test_WithOutputFrom_TracksExecutorBinding() .BindExecutor(future) .Build(); - workflow.OutputExecutors.Should().ContainKey("future"); - workflow.OutputExecutors["future"].Should().BeEquivalentTo([OutputTag.Intermediate]); + Assert.Contains("future", workflow.OutputExecutors); + Assert.Equivalent(new[] { OutputTag.Intermediate }, workflow.OutputExecutors["future"]); } } @@ -641,7 +639,7 @@ public void Build_WhenValidationFails_DoesNotMarkCoreWorkflow() Action build = () => builder.Build(); // Assert - build.Should().Throw(); + Assert.Throws(build); Assert.Equal(BigInteger.Zero, GetFeatureMask()); } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowHostSmokeTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowHostSmokeTests.cs index 92ebc5915d8..a5d25a1246b 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowHostSmokeTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowHostSmokeTests.cs @@ -7,7 +7,6 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.Extensions.AI; namespace Microsoft.Agents.AI.Workflows.UnitTests; @@ -303,14 +302,13 @@ public async Task Test_AsAgent_ErrorContentStreamedOutAsync(bool includeExceptio if (update.Contents.Any()) { // We should expect a single update which contains the error content. - update.Contents.Should().ContainSingle() - .Which.Should().BeOfType() - .Which.Message.Should().Be(expectedMessage); + ErrorContent errorContent = Assert.IsType(Assert.Single(update.Contents)); + Assert.Equal(expectedMessage, errorContent.Message); hadErrorContent = true; } } - hadErrorContent.Should().BeTrue(); + Assert.True(hadErrorContent); } /// @@ -338,15 +336,13 @@ public async Task Test_AsAgent_FunctionCallContentPreservedInRequestInfoAsync() AgentResponseUpdate? updateWithFunctionCall = updates.FirstOrDefault(u => u.RawRepresentation is RequestInfoEvent && u.Contents.Any(c => c is FunctionCallContent)); - updateWithFunctionCall.Should().NotBeNull("a FunctionCallContent should be present in the response updates"); - FunctionCallContent retrievedContent = updateWithFunctionCall!.Contents - .OfType() - .Should().ContainSingle() - .Which; + Assert.NotNull(updateWithFunctionCall); + FunctionCallContent retrievedContent = Assert.Single(updateWithFunctionCall!.Contents + .OfType()); - retrievedContent.CallId.Should().NotBe(CallId); - retrievedContent.CallId.Should().EndWith($":{CallId}"); - retrievedContent.Name.Should().Be(FunctionName); + Assert.NotEqual(CallId, retrievedContent.CallId); + Assert.EndsWith($":{CallId}", retrievedContent.CallId); + Assert.Equal(FunctionName, retrievedContent.Name); } /// @@ -374,15 +370,13 @@ public async Task Test_AsAgent_ToolApprovalRequestContentPreservedInRequestInfoA AgentResponseUpdate? updateWithUserInput = updates.FirstOrDefault(u => u.RawRepresentation is RequestInfoEvent && u.Contents.Any(c => c is ToolApprovalRequestContent)); - updateWithUserInput.Should().NotBeNull("a ToolApprovalRequestContent should be present in the response updates"); - ToolApprovalRequestContent retrievedContent = updateWithUserInput!.Contents - .OfType() - .Should().ContainSingle() - .Which; + Assert.NotNull(updateWithUserInput); + ToolApprovalRequestContent retrievedContent = Assert.Single(updateWithUserInput!.Contents + .OfType()); - retrievedContent.Should().NotBeNull(); - retrievedContent.RequestId.Should().NotBe(RequestId); - retrievedContent.RequestId.Should().EndWith($":{RequestId}"); + Assert.NotNull(retrievedContent); + Assert.NotEqual(RequestId, retrievedContent.RequestId); + Assert.EndsWith($":{RequestId}", retrievedContent.RequestId); } /// @@ -410,12 +404,12 @@ public async Task Test_AsAgent_FunctionCallRoundtrip_ResponseIsProcessedAsync() // Assert 1: We should have received a FunctionCallContent AgentResponseUpdate? updateWithRequest = firstCallUpdates.FirstOrDefault(u => u.RawRepresentation is RequestInfoEvent && u.Contents.Any(c => c is FunctionCallContent)); - updateWithRequest.Should().NotBeNull("a FunctionCallContent should be present in the response updates"); + Assert.NotNull(updateWithRequest); FunctionCallContent receivedRequest = updateWithRequest!.Contents .OfType() .First(); - receivedRequest.CallId.Should().EndWith($":{CallId}"); + Assert.EndsWith($":{CallId}", receivedRequest.CallId); // Act 2: Send the response back FunctionResultContent responseContent = new(receivedRequest.CallId, "test result"); @@ -426,13 +420,11 @@ public async Task Test_AsAgent_FunctionCallRoundtrip_ResponseIsProcessedAsync() // Assert 2: The response should be processed and the original request should no longer be pending. // Concretely, the workflow should not re-emit a FunctionCallContent with the same CallId. - secondCallUpdates.Should().NotBeNull("processing the response should produce updates"); - secondCallUpdates.Should().NotBeEmpty("processing the response should progress the workflow"); - secondCallUpdates + Assert.NotNull(secondCallUpdates); + Assert.NotEmpty(secondCallUpdates); + Assert.DoesNotContain(secondCallUpdates .Where(u => u.RawRepresentation is RequestInfoEvent) - .SelectMany(u => u.Contents.OfType()) - .Should() - .NotContain(c => c.CallId == receivedRequest.CallId, "the external FunctionCallContent request should be cleared after processing the response"); + .SelectMany(u => u.Contents.OfType()), c => c.CallId == receivedRequest.CallId); } /// @@ -461,12 +453,12 @@ public async Task Test_AsAgent_ToolApprovalRoundtrip_ResponseIsProcessedAsync() // Assert 1: We should have received a ToolApprovalRequestContent AgentResponseUpdate? updateWithRequest = firstCallUpdates.FirstOrDefault(u => u.RawRepresentation is RequestInfoEvent && u.Contents.Any(c => c is ToolApprovalRequestContent)); - updateWithRequest.Should().NotBeNull("a ToolApprovalRequestContent should be present in the response updates"); + Assert.NotNull(updateWithRequest); ToolApprovalRequestContent receivedRequest = updateWithRequest!.Contents .OfType() .First(); - receivedRequest.RequestId.Should().EndWith($":{RequestId}"); + Assert.EndsWith($":{RequestId}", receivedRequest.RequestId); // Act 2: Send the response back - use CreateResponse to get the right response type ToolApprovalResponseContent responseContent = receivedRequest.CreateResponse(approved: true); @@ -476,11 +468,11 @@ public async Task Test_AsAgent_ToolApprovalRoundtrip_ResponseIsProcessedAsync() List secondCallUpdates = await agent.RunStreamingAsync(responseMessage, session).ToListAsync(); // Assert 2: The response should be applied so that the original request is no longer pending - secondCallUpdates.Should().NotBeEmpty("handling the user input response should produce follow-up updates"); + Assert.NotEmpty(secondCallUpdates); bool requestStillPresent = secondCallUpdates.Any(u => u.RawRepresentation is RequestInfoEvent && u.Contents.OfType().Any(r => r.RequestId == receivedRequest.RequestId)); - requestStillPresent.Should().BeFalse("the original ToolApprovalRequestContent should not be re-emitted after its response is processed"); + Assert.False(requestStillPresent); } /// @@ -515,8 +507,7 @@ public async Task Test_AsAgent_MixedResponseAndRegularMessage_BothProcessedAsync u.RawRepresentation is RequestInfoEvent && u.Contents.Any(c => c is FunctionCallContent)); FunctionCallContent emittedRequest = requestUpdate.Contents.OfType().Single(); - firstCallUpdates.Should().Contain(u => u.Contents.Any(c => c is FunctionCallContent), - "the first call should emit a FunctionCallContent request"); + Assert.Contains(firstCallUpdates, u => u.Contents.Any(c => c is FunctionCallContent)); // Act 2: Send a mixed message containing both the function result AND regular non-response content FunctionResultContent responseContent = new(emittedRequest.CallId, "tool output"); @@ -525,16 +516,12 @@ public async Task Test_AsAgent_MixedResponseAndRegularMessage_BothProcessedAsync List secondCallUpdates = await agent.RunStreamingAsync(mixedMessage, session).ToListAsync(); // Assert 2: The workflow should have processed both parts without errors - secondCallUpdates.Should().NotBeEmpty("the mixed message should produce follow-up updates"); - secondCallUpdates + Assert.NotEmpty(secondCallUpdates); + Assert.DoesNotContain(secondCallUpdates .Where(u => u.RawRepresentation is RequestInfoEvent) - .SelectMany(u => u.Contents.OfType()) - .Should() - .NotContain(c => c.CallId == emittedRequest.CallId, "the external FunctionCallContent should be cleared after the response is processed"); - secondCallUpdates - .SelectMany(u => u.Contents.OfType()) - .Should() - .BeEmpty("no workflow errors should occur when processing a mixed response-and-regular message"); + .SelectMany(u => u.Contents.OfType()), c => c.CallId == emittedRequest.CallId); + Assert.Empty(secondCallUpdates + .SelectMany(u => u.Contents.OfType()) ?? []); } [Fact] @@ -564,16 +551,12 @@ public async Task Test_AsAgent_ResponseThenRegularAcrossMessages_NoDuplicateFunc List secondCallUpdates = await agent.RunStreamingAsync(resumeMessages, session).ToListAsync(); - secondCallUpdates.Should().NotBeEmpty(); - secondCallUpdates + Assert.NotEmpty(secondCallUpdates); + Assert.DoesNotContain(secondCallUpdates .Where(u => u.RawRepresentation is RequestInfoEvent) - .SelectMany(u => u.Contents.OfType()) - .Should() - .NotContain(c => c.CallId == emittedRequest.CallId, "response+regular content split across messages should not re-emit the handled external request"); - secondCallUpdates - .SelectMany(u => u.Contents.OfType()) - .Should() - .BeEmpty(); + .SelectMany(u => u.Contents.OfType()), c => c.CallId == emittedRequest.CallId); + Assert.Empty(secondCallUpdates + .SelectMany(u => u.Contents.OfType()) ?? []); } [Fact] @@ -604,7 +587,7 @@ public async Task Test_AsAgent_MatchingResponse_DoesNotCauseExtraTurnAsync() .SelectMany(u => u.Contents.OfType()) .Count(c => c.CallId == emittedRequest.CallId); - functionCallCount.Should().Be(1, "a matching external response should not trigger an extra TurnToken-driven turn"); + Assert.Equal(1, functionCallCount); } [Fact] @@ -656,14 +639,12 @@ public async Task Test_AsAgent_MixedResponseAndRegularMessage_CrossExecutorStart List secondCallUpdates = await agent.RunStreamingAsync(resumeMessages, session).ToListAsync(); List textContents = [.. secondCallUpdates.SelectMany(update => update.Contents.OfType()).Select(content => content.Text)]; - textContents.Should().Contain(ResumeProcessedText, "the start executor should receive an explicit TurnToken when the matched response wakes a different executor"); - textContents.Should().Contain("Request processed", "the matched external response should still be delivered to the downstream request owner"); - secondCallUpdates + Assert.Contains(ResumeProcessedText, textContents); + Assert.Contains("Request processed", textContents); + Assert.DoesNotContain(secondCallUpdates .Where(u => u.RawRepresentation is RequestInfoEvent) - .SelectMany(u => u.Contents.OfType()) - .Should() - .NotContain(c => c.CallId == emittedRequest.CallId, "the handled external request should not be re-emitted while waking the start executor"); - secondCallUpdates.SelectMany(u => u.Contents.OfType()).Should().BeEmpty(); + .SelectMany(u => u.Contents.OfType()), c => c.CallId == emittedRequest.CallId); + Assert.Empty(secondCallUpdates.SelectMany(u => u.Contents.OfType()) ?? []); } [Fact] @@ -680,7 +661,7 @@ public async Task Test_AsAgent_UnmatchedResponse_TriggersTurnAndKeepsProgressing AgentSession session = await agent.CreateSessionAsync(); List firstCallUpdates = await agent.RunStreamingAsync(new ChatMessage(ChatRole.User, "Start"), session).ToListAsync(); - firstCallUpdates.Should().Contain(u => u.Contents.Any(c => c is FunctionCallContent)); + Assert.Contains(firstCallUpdates, u => u.Contents.Any(c => c is FunctionCallContent)); List secondCallUpdates = await agent.RunStreamingAsync( new ChatMessage(ChatRole.Tool, [new FunctionResultContent("different-call-id", "tool output")]), @@ -690,8 +671,8 @@ public async Task Test_AsAgent_UnmatchedResponse_TriggersTurnAndKeepsProgressing .SelectMany(u => u.Contents.OfType()) .Count(c => c.CallId == CallId); - functionCallCount.Should().Be(1, "an unmatched response should be treated as regular input and still drive a TurnToken continuation without workflow errors"); - secondCallUpdates.SelectMany(u => u.Contents.OfType()).Should().BeEmpty(); + Assert.Equal(1, functionCallCount); + Assert.Empty(secondCallUpdates.SelectMany(u => u.Contents.OfType()) ?? []); } /// @@ -744,14 +725,10 @@ public async Task Test_AsAgent_ResponseOnlyToNonStartExecutor_StartExecutorIsSti .SelectMany(u => u.Contents.OfType()) .Select(c => c.Text)]; - textContents.Should().Contain("Request processed", - "the downstream executor should process the external response"); - textContents.Should().Contain(ActivatedMarker, - "the start executor should receive a TurnToken and be activated even when resume contains only an external response"); - secondCallUpdates - .SelectMany(u => u.Contents.OfType()) - .Should() - .BeEmpty(); + Assert.Contains("Request processed", textContents); + Assert.Contains(ActivatedMarker, textContents); + Assert.Empty(secondCallUpdates + .SelectMany(u => u.Contents.OfType()) ?? []); } [Theory] @@ -761,7 +738,7 @@ public async Task Test_AsAgent_FailsWhenNotChatProtocolAsync(bool runAsync) { // Arrange NonChatProtocolExecutor executor = new(); - executor.DescribeProtocol().IsChatProtocol().Should().BeFalse(); + Assert.False(executor.DescribeProtocol().IsChatProtocol()); Workflow workflow = new WorkflowBuilder(executor).Build(); AIAgent workflowAsAgent = workflow.AsAIAgent(); @@ -770,7 +747,7 @@ public async Task Test_AsAgent_FailsWhenNotChatProtocolAsync(bool runAsync) ? () => workflowAsAgent.RunStreamingAsync().ToAgentResponseAsync() : () => workflowAsAgent.RunAsync(); - await action.Should().ThrowAsync(); + await Assert.ThrowsAsync(action); } private async Task Run_AsAgent_OutgoingMessagesInHistoryAsync(Workflow workflow, bool runAsync) @@ -804,7 +781,7 @@ private async Task Run_AsAgent_OutgoingMessagesInHistoryAsync(Workflow workflow, } // Assert - WorkflowSession workflowSession = session.Should().BeOfType().Subject; + WorkflowSession workflowSession = Assert.IsType(session); ChatMessage[] responseMessages = response.Messages.Where(message => message.Contents.Any()) .ToArray(); @@ -814,7 +791,7 @@ private async Task Run_AsAgent_OutgoingMessagesInHistoryAsync(Workflow workflow, // Since we never sent an incoming message, the expectation is that there should be nothing in the session // except the response - responseMessages.Should().BeEquivalentTo(sessionMessages, options => options.WithStrictOrdering()); + Assert.Equivalent(sessionMessages, responseMessages); } [Theory] @@ -858,9 +835,8 @@ public async Task Test_AsAgent_UsesDesignatedWorkflowOutputInsteadOfIntermediate .AsAIAgent("WorkflowAgent") .RunAsync(new ChatMessage(ChatRole.User, "hello")); - response.Text.Should().Be("SECOND ANSWER"); - response.Messages.Should().ContainSingle() - .Which.Text.Should().Be("SECOND ANSWER"); + Assert.Equal("SECOND ANSWER", response.Text); + Assert.Equal("SECOND ANSWER", Assert.Single(response.Messages).Text); } // ----- Phase 5: Workflow-as-Agent intermediate forwarding ----------------- @@ -896,9 +872,8 @@ public async Task Test_WorkflowHostAgent_IntermediateAgentResponseForwardedInStr // the surfaced event for consumers that care to distinguish. List updates = await RunStreamingAsync(workflow, includeWorkflowOutputsInResponse: false); - updates.Count(u => u.Text == InterText).Should().Be(1); - updates.Any(u => u.RawRepresentation is AgentResponseEvent are && are.IsIntermediate() && u.Contents.Count == 0) - .Should().BeTrue("the completion event remains observable without duplicating streamed text"); + Assert.Equal(1, updates.Count(u => u.Text == InterText)); + Assert.Contains(updates, u => u.RawRepresentation is AgentResponseEvent are && are.IsIntermediate() && u.Contents.Count == 0); } [Fact] @@ -915,9 +890,8 @@ public async Task Test_WorkflowHostAgent_TerminalAgentResponseForwardedUnconditi // asymmetry between AgentResponse and AgentResponseUpdate is gone under Futures-on. List updates = await RunStreamingAsync(workflow, includeWorkflowOutputsInResponse: false); - updates.Count(u => u.Text == FinalText).Should().Be(1); - updates.Any(u => u.RawRepresentation is AgentResponseEvent && u.Contents.Count == 0) - .Should().BeTrue("the completion event remains observable without duplicating streamed text"); + Assert.Equal(1, updates.Count(u => u.Text == FinalText)); + Assert.Contains(updates, u => u.RawRepresentation is AgentResponseEvent && u.Contents.Count == 0); } [Fact] @@ -932,8 +906,7 @@ public async Task Test_WorkflowHostAgent_EmptyAgentResponseDoesNotCreateObservab List updates = await RunStreamingAsync(workflow, includeWorkflowOutputsInResponse: false); - updates.Any(u => u.RawRepresentation is AgentResponseEvent) - .Should().BeFalse("an empty response did not previously produce an observable completion update"); + Assert.DoesNotContain(updates, u => u.RawRepresentation is AgentResponseEvent); } [Fact] @@ -951,13 +924,11 @@ static Workflow Build() // Legacy semantics: AgentResponseEvent stays behind the include flag when Futures // is off. Two fresh workflows because in-process runs aren't reentrant. List gated = await RunStreamingAsync(Build(), includeWorkflowOutputsInResponse: false); - gated.Any(u => u.RawRepresentation is AgentResponseEvent && u.Text == FinalText) - .Should().BeFalse("terminal AgentResponseEvent stays gated under Futures-off"); + Assert.DoesNotContain(gated, u => u.RawRepresentation is AgentResponseEvent && u.Text == FinalText); List included = await RunStreamingAsync(Build(), includeWorkflowOutputsInResponse: true); - included.Count(u => u.Text == FinalText).Should().Be(1); - included.Any(u => u.RawRepresentation is AgentResponseEvent && u.Contents.Count == 0) - .Should().BeTrue("opting in preserves the completion event without duplicating streamed text"); + Assert.Equal(1, included.Count(u => u.Text == FinalText)); + Assert.Contains(included, u => u.RawRepresentation is AgentResponseEvent && u.Contents.Count == 0); } [Fact] @@ -971,8 +942,7 @@ public async Task Test_WorkflowHostAgent_UndesignatedExecutorEmitsNoAgentRespons List updates = await RunStreamingAsync(workflow, includeWorkflowOutputsInResponse: true); - updates.Any(u => u.RawRepresentation is AgentResponseEvent) - .Should().BeFalse("an undesignated AIAgent executor produces no AgentResponseEvent under Futures-on"); + Assert.DoesNotContain(updates, u => u.RawRepresentation is AgentResponseEvent); } [Fact] @@ -985,9 +955,8 @@ public async Task Test_WorkflowHostAgent_UndesignatedAgentResponseSurfacesWhenFu List updates = await RunStreamingAsync(workflow, includeWorkflowOutputsInResponse: true); - updates.Count(u => u.Text == InterText).Should().Be(1); - updates.Any(u => u.RawRepresentation is AgentResponseEvent && u.Contents.Count == 0) - .Should().BeTrue("legacy bypass preserves the completion event without duplicating streamed text"); + Assert.Equal(1, updates.Count(u => u.Text == InterText)); + Assert.Contains(updates, u => u.RawRepresentation is AgentResponseEvent && u.Contents.Count == 0); } [Fact] @@ -1004,8 +973,8 @@ public async Task Test_WorkflowHostAgent_IntermediateTagAvailableViaRawRepresent AgentResponseUpdate progress = updates.First(u => u.RawRepresentation is AgentResponseEvent); AgentResponseEvent raw = (AgentResponseEvent)progress.RawRepresentation!; - raw.IsIntermediate().Should().BeTrue(); - raw.Tags.Should().BeEquivalentTo(new[] { OutputTag.Intermediate }); + Assert.True(raw.IsIntermediate()); + Assert.Equivalent(new[] { OutputTag.Intermediate }, raw.Tags); } [Fact] @@ -1017,10 +986,9 @@ public async Task Test_WorkflowHostAgent_DistinctCompletedResponseFromSameExecut List updates = await RunStreamingAsync(workflow, includeWorkflowOutputsInResponse: true); - updates.Count(u => u.Text == InterText).Should().Be(1); - updates.Count(u => u.Text == FinalText).Should().Be(1); - updates.Any(u => u.RawRepresentation is AgentResponseEvent && u.Text == FinalText) - .Should().BeTrue("a different response from the same executor must not be suppressed"); + Assert.Equal(1, updates.Count(u => u.Text == InterText)); + Assert.Equal(1, updates.Count(u => u.Text == FinalText)); + Assert.Contains(updates, u => u.RawRepresentation is AgentResponseEvent && u.Text == FinalText); } [Fact] @@ -1032,10 +1000,9 @@ public async Task Test_WorkflowHostAgent_DistinctCompletedMessageFromSameRespons List updates = await RunStreamingAsync(workflow, includeWorkflowOutputsInResponse: true); - updates.Count(u => u.Text == InterText).Should().Be(1); - updates.Count(u => u.Text == FinalText).Should().Be(1); - updates.Any(u => u.RawRepresentation is AgentResponseEvent && u.Text == FinalText) - .Should().BeTrue("a response ID alone must not suppress a distinct completed message"); + Assert.Equal(1, updates.Count(u => u.Text == InterText)); + Assert.Equal(1, updates.Count(u => u.Text == FinalText)); + Assert.Contains(updates, u => u.RawRepresentation is AgentResponseEvent && u.Text == FinalText); } [Fact] @@ -1053,10 +1020,9 @@ public async Task Test_WorkflowHostAgent_WhitespaceMessageIdDoesNotSuppressCompl List updates = await RunStreamingAsync(workflow, includeWorkflowOutputsInResponse: true); - updates.Count(u => u.Text == InterText).Should().Be(1); - updates.Count(u => u.Text == FinalText).Should().Be(1); - updates.Any(u => u.RawRepresentation is AgentResponseEvent && u.Text == FinalText) - .Should().BeTrue("whitespace-only message IDs cannot reliably correlate streamed and completed messages"); + Assert.Equal(1, updates.Count(u => u.Text == InterText)); + Assert.Equal(1, updates.Count(u => u.Text == FinalText)); + Assert.Contains(updates, u => u.RawRepresentation is AgentResponseEvent && u.Text == FinalText); } [Fact] @@ -1068,10 +1034,9 @@ public async Task Test_WorkflowHostAgent_UnstreamedMessageFromSameResponseIsForw List updates = await RunStreamingAsync(workflow, includeWorkflowOutputsInResponse: true); - updates.Count(u => u.Text == InterText).Should().Be(1); - updates.Count(u => u.Text == FinalText).Should().Be(1); - updates.Any(u => u.RawRepresentation is AgentResponseEvent && u.Text == FinalText) - .Should().BeTrue("only the correlated message in a multi-message response should be suppressed"); + Assert.Equal(1, updates.Count(u => u.Text == InterText)); + Assert.Equal(1, updates.Count(u => u.Text == FinalText)); + Assert.Contains(updates, u => u.RawRepresentation is AgentResponseEvent && u.Text == FinalText); } private sealed class StreamThenCompleteExecutor( diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowRunActivityStopTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowRunActivityStopTests.cs index f35910f26b5..f70c9294d5c 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowRunActivityStopTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowRunActivityStopTests.cs @@ -5,7 +5,6 @@ using System.Diagnostics; using System.Linq; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.Agents.AI.Workflows.Observability; namespace Microsoft.Agents.AI.Workflows.UnitTests; @@ -83,28 +82,26 @@ public async Task WorkflowRunActivity_IsStopped_LockstepAsync() .Where(a => a.RootId == testActivity.RootId && a.OperationName.StartsWith(ActivityNames.WorkflowSession, StringComparison.Ordinal)) .ToList(); - startedSessions.Should().HaveCount(1, "workflow.session Activity should be started"); + Assert.Single(startedSessions ?? []); var stoppedSessions = this._stoppedActivities .Where(a => a.RootId == testActivity.RootId && a.OperationName.StartsWith(ActivityNames.WorkflowSession, StringComparison.Ordinal)) .ToList(); - stoppedSessions.Should().HaveCount(1, - "workflow.session Activity should be stopped/disposed so it is exported to telemetry backends"); + Assert.Single(stoppedSessions ?? []); // Assert - workflow_invoke should have been started and stopped var startedWorkflowRuns = this._startedActivities .Where(a => a.RootId == testActivity.RootId && a.OperationName.StartsWith(ActivityNames.WorkflowInvoke, StringComparison.Ordinal)) .ToList(); - startedWorkflowRuns.Should().HaveCount(1, "workflow_invoke Activity should be started"); + Assert.Single(startedWorkflowRuns ?? []); var stoppedWorkflowRuns = this._stoppedActivities .Where(a => a.RootId == testActivity.RootId && a.OperationName.StartsWith(ActivityNames.WorkflowInvoke, StringComparison.Ordinal)) .ToList(); - stoppedWorkflowRuns.Should().HaveCount(1, - "workflow_invoke Activity should be stopped/disposed so it is exported to telemetry backends (issue #4155)"); + Assert.Single(stoppedWorkflowRuns ?? []); } /// @@ -127,28 +124,26 @@ public async Task WorkflowRunActivity_IsStopped_OffThreadAsync() .Where(a => a.RootId == testActivity.RootId && a.OperationName.StartsWith(ActivityNames.WorkflowSession, StringComparison.Ordinal)) .ToList(); - startedSessions.Should().HaveCount(1, "workflow.session Activity should be started"); + Assert.Single(startedSessions ?? []); var stoppedSessions = this._stoppedActivities .Where(a => a.RootId == testActivity.RootId && a.OperationName.StartsWith(ActivityNames.WorkflowSession, StringComparison.Ordinal)) .ToList(); - stoppedSessions.Should().HaveCount(1, - "workflow.session Activity should be stopped/disposed so it is exported to telemetry backends"); + Assert.Single(stoppedSessions ?? []); // Assert - workflow_invoke should have been started and stopped var startedWorkflowRuns = this._startedActivities .Where(a => a.RootId == testActivity.RootId && a.OperationName.StartsWith(ActivityNames.WorkflowInvoke, StringComparison.Ordinal)) .ToList(); - startedWorkflowRuns.Should().HaveCount(1, "workflow_invoke Activity should be started"); + Assert.Single(startedWorkflowRuns ?? []); var stoppedWorkflowRuns = this._stoppedActivities .Where(a => a.RootId == testActivity.RootId && a.OperationName.StartsWith(ActivityNames.WorkflowInvoke, StringComparison.Ordinal)) .ToList(); - stoppedWorkflowRuns.Should().HaveCount(1, - "workflow_invoke Activity should be stopped/disposed so it is exported to telemetry backends (issue #4155)"); + Assert.Single(stoppedWorkflowRuns ?? []); } /// @@ -180,22 +175,21 @@ public async Task WorkflowRunActivity_IsStopped_Streaming_OffThreadAsync() .Where(a => a.RootId == testActivity.RootId && a.OperationName.StartsWith(ActivityNames.WorkflowSession, StringComparison.Ordinal)) .ToList(); - startedSessions.Should().HaveCount(1, "workflow.session Activity should be started"); + Assert.Single(startedSessions ?? []); // Assert - workflow_invoke should have been started var startedWorkflowRuns = this._startedActivities .Where(a => a.RootId == testActivity.RootId && a.OperationName.StartsWith(ActivityNames.WorkflowInvoke, StringComparison.Ordinal)) .ToList(); - startedWorkflowRuns.Should().HaveCount(1, "workflow_invoke Activity should be started"); + Assert.Single(startedWorkflowRuns ?? []); // Assert - workflow_invoke should have been stopped var stoppedWorkflowRuns = this._stoppedActivities .Where(a => a.RootId == testActivity.RootId && a.OperationName.StartsWith(ActivityNames.WorkflowInvoke, StringComparison.Ordinal)) .ToList(); - stoppedWorkflowRuns.Should().HaveCount(1, - "workflow_invoke Activity should be stopped/disposed so it is exported to telemetry backends (issue #4155)"); + Assert.Single(stoppedWorkflowRuns ?? []); } /// @@ -234,30 +228,26 @@ public async Task WorkflowRunActivity_IsStopped_Streaming_OffThread_MultiTurnAsy .Where(a => a.RootId == testActivity.RootId && a.OperationName.StartsWith(ActivityNames.WorkflowSession, StringComparison.Ordinal)) .ToList(); - startedSessions.Should().HaveCount(2, - "each streaming invocation should start its own workflow.session Activity"); + Assert.Equal(2, startedSessions.Count); var stoppedSessions = this._stoppedActivities .Where(a => a.RootId == testActivity.RootId && a.OperationName.StartsWith(ActivityNames.WorkflowSession, StringComparison.Ordinal)) .ToList(); - stoppedSessions.Should().HaveCount(2, - "each workflow.session Activity should be stopped/disposed so it is exported to telemetry backends"); + Assert.Equal(2, stoppedSessions.Count); // Assert - two workflow_invoke activities should have been started and stopped var startedWorkflowRuns = this._startedActivities .Where(a => a.RootId == testActivity.RootId && a.OperationName.StartsWith(ActivityNames.WorkflowInvoke, StringComparison.Ordinal)) .ToList(); - startedWorkflowRuns.Should().HaveCount(2, - "each streaming invocation should start its own workflow_invoke Activity"); + Assert.Equal(2, startedWorkflowRuns.Count); var stoppedWorkflowRuns = this._stoppedActivities .Where(a => a.RootId == testActivity.RootId && a.OperationName.StartsWith(ActivityNames.WorkflowInvoke, StringComparison.Ordinal)) .ToList(); - stoppedWorkflowRuns.Should().HaveCount(2, - "each workflow_invoke Activity should be stopped/disposed so it is exported to telemetry backends in multi-turn scenarios"); + Assert.Equal(2, stoppedWorkflowRuns.Count); } /// @@ -293,9 +283,7 @@ public async Task AllActivities_AreStopped_AfterWorkflowCompletionAsync() .Where(a => neverStopped.Contains(a.Id)) .Select(a => a.OperationName) .ToList(); - neverStoppedNames.Should().BeEmpty( - "all started activities should be stopped so they are exported. " + - $"Activities started but never stopped: [{string.Join(", ", neverStoppedNames)}]"); + Assert.Empty(neverStoppedNames ?? []); } } @@ -327,18 +315,16 @@ public async Task Lockstep_SessionActivity_DoesNotLeak_IntoCaller_ActivityCurren .Where(a => a.RootId == testActivity.RootId && a.OperationName.StartsWith(ActivityNames.WorkflowSession, StringComparison.Ordinal)) .ToList(); - sessionActivities.Should().HaveCount(1, "one session activity should exist"); + Assert.Single(sessionActivities); - appActivity.ParentId.Should().Be(testActivity.Id, - "application activity should be parented under the test root, not the workflow session"); + Assert.Equal(testActivity.Id, appActivity.ParentId); // Assert — the run activity should still be parented under the session var invokeActivities = this._startedActivities .Where(a => a.RootId == testActivity.RootId && a.OperationName.StartsWith(ActivityNames.WorkflowInvoke, StringComparison.Ordinal)) .ToList(); - invokeActivities.Should().HaveCount(1, "one workflow_invoke activity should exist"); - invokeActivities[0].ParentId.Should().Be(sessionActivities[0].Id, - "workflow_invoke activity should be nested under the session activity"); + var invokeActivity = Assert.Single(invokeActivities); + Assert.Equal(Assert.Single(sessionActivities).Id, invokeActivity.ParentId); } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowSessionTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowSessionTests.cs index 3178b541294..6059347375e 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowSessionTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowSessionTests.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; -using FluentAssertions; using Microsoft.Agents.AI.Workflows.Checkpointing; using Microsoft.Extensions.AI; @@ -30,7 +29,7 @@ public void ResolveEnvelopeType_ReturnsPortTypeWhenPortDeclaresMatchingType() Dictionary ports = new() { [port.Id] = port }; RequestPortInfo portInfo = new(new TypeId(live), new TypeId(typeof(object)), port.Id); - WorkflowSession.ResolveEnvelopeType(portInfo, ports).Should().Be(live); + Assert.Equal(live, WorkflowSession.ResolveEnvelopeType(portInfo, ports)); } [Fact] @@ -44,7 +43,7 @@ public void ResolveEnvelopeType_ResolvesAcrossAssemblyVersionMutation() string mutatedAssemblyName = $"{simpleAssemblyName}, Version=99.0.0.0, Culture=neutral, PublicKeyToken=null"; RequestPortInfo portInfo = new(new TypeId(mutatedAssemblyName, live.FullName!), new TypeId(typeof(object)), port.Id); - WorkflowSession.ResolveEnvelopeType(portInfo, ports).Should().Be(live); + Assert.Equal(live, WorkflowSession.ResolveEnvelopeType(portInfo, ports)); } [Fact] @@ -59,7 +58,7 @@ public void ResolveEnvelopeType_ResolvesAcrossGenericArgumentVersionMutation() string mutatedTypeName = $"System.Collections.Generic.List`1[[Microsoft.Extensions.AI.ChatMessage, {innerSimpleName}, Version=99.0.0.0, Culture=neutral, PublicKeyToken=null]]"; RequestPortInfo portInfo = new(new TypeId(outerSimpleName, mutatedTypeName), new TypeId(typeof(object)), port.Id); - WorkflowSession.ResolveEnvelopeType(portInfo, ports).Should().Be(live); + Assert.Equal(live, WorkflowSession.ResolveEnvelopeType(portInfo, ports)); } [Fact] @@ -71,7 +70,7 @@ public void ResolveEnvelopeType_ReturnsNullWhenPortIdIsUnknown() }; RequestPortInfo portInfo = new(new TypeId(typeof(TestEnvelope)), new TypeId(typeof(object)), "missing-port"); - WorkflowSession.ResolveEnvelopeType(portInfo, ports).Should().BeNull(); + Assert.Null(WorkflowSession.ResolveEnvelopeType(portInfo, ports)); } [Fact] @@ -83,7 +82,7 @@ public void ResolveEnvelopeType_ReturnsNullWhenRecordedTypeDoesNotMatchPortType( }; RequestPortInfo portInfo = new(new TypeId("Some.Unloaded.Assembly", "Some.Unknown.Type"), new TypeId(typeof(object)), "port-1"); - WorkflowSession.ResolveEnvelopeType(portInfo, ports).Should().BeNull(); + Assert.Null(WorkflowSession.ResolveEnvelopeType(portInfo, ports)); } [Fact] @@ -93,8 +92,8 @@ public void TryGetRequestEnvelope_ReturnsEnvelopeWhenPortDeclaresEnvelopeType() Dictionary ports = new() { [port.Id] = port }; ExternalRequest request = ExternalRequest.Create(port, new TestEnvelope()); - WorkflowSession.TryGetRequestEnvelope(request, ports, out IExternalRequestEnvelope? envelope).Should().BeTrue(); - envelope.Should().BeOfType(); + Assert.True(WorkflowSession.TryGetRequestEnvelope(request, ports, out IExternalRequestEnvelope? envelope)); + Assert.True((envelope) is TestEnvelope); } [Fact] @@ -104,8 +103,8 @@ public void TryGetRequestEnvelope_ReturnsFalseWhenPortTypeIsNotEnvelope() Dictionary ports = new() { [port.Id] = port }; ExternalRequest request = ExternalRequest.Create(port, "not-an-envelope"); - WorkflowSession.TryGetRequestEnvelope(request, ports, out IExternalRequestEnvelope? envelope).Should().BeFalse(); - envelope.Should().BeNull(); + Assert.False(WorkflowSession.TryGetRequestEnvelope(request, ports, out IExternalRequestEnvelope? envelope)); + Assert.Null(envelope); } [Fact] @@ -116,7 +115,7 @@ public void TryGetRequestEnvelope_ReturnsFalseWhenRecordedTypeDoesNotMatchPortTy RequestPortInfo recordedPortInfo = new(new TypeId("Some.Unloaded.Assembly", "Some.Unknown.Type"), new TypeId(typeof(object)), port.Id); ExternalRequest request = new(recordedPortInfo, "req-1", new PortableValue(new TestEnvelope())); - WorkflowSession.TryGetRequestEnvelope(request, ports, out IExternalRequestEnvelope? envelope).Should().BeFalse(); - envelope.Should().BeNull(); + Assert.False(WorkflowSession.TryGetRequestEnvelope(request, ports, out IExternalRequestEnvelope? envelope)); + Assert.Null(envelope); } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowVisualizerTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowVisualizerTests.cs index c8cf2cf214f..a6a486c60d1 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowVisualizerTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowVisualizerTests.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. using System; -using FluentAssertions; namespace Microsoft.Agents.AI.Workflows.UnitTests; @@ -35,12 +34,12 @@ public void Test_WorkflowViz_ToDotString_Basic() var dotContent = workflow.ToDotString(); // Check that the DOT content contains expected elements - dotContent.Should().Contain("digraph Workflow {"); - dotContent.Should().Contain("\"executor1\""); - dotContent.Should().Contain("\"executor2\""); - dotContent.Should().Contain("\"executor1\" -> \"executor2\""); - dotContent.Should().Contain("fillcolor=lightgreen"); // Start executor styling - dotContent.Should().Contain("(Start)"); + Assert.Contains("digraph Workflow {", dotContent); + Assert.Contains("\"executor1\"", dotContent); + Assert.Contains("\"executor2\"", dotContent); + Assert.Contains("\"executor1\" -> \"executor2\"", dotContent); + Assert.Contains("fillcolor=lightgreen", dotContent); // Start executor styling + Assert.Contains("(Start)", dotContent); } [Fact] @@ -62,19 +61,19 @@ public void Test_WorkflowViz_Complex_Workflow() var dotContent = workflow.ToDotString(); // Check all executors are present - dotContent.Should().Contain("\"start\""); - dotContent.Should().Contain("\"middle1\""); - dotContent.Should().Contain("\"middle2\""); - dotContent.Should().Contain("\"end\""); + Assert.Contains("\"start\"", dotContent); + Assert.Contains("\"middle1\"", dotContent); + Assert.Contains("\"middle2\"", dotContent); + Assert.Contains("\"end\"", dotContent); // Check all edges are present - dotContent.Should().Contain("\"start\" -> \"middle1\""); - dotContent.Should().Contain("\"start\" -> \"middle2\""); - dotContent.Should().Contain("\"middle1\" -> \"end\""); - dotContent.Should().Contain("\"middle2\" -> \"end\""); + Assert.Contains("\"start\" -> \"middle1\"", dotContent); + Assert.Contains("\"start\" -> \"middle2\"", dotContent); + Assert.Contains("\"middle1\" -> \"end\"", dotContent); + Assert.Contains("\"middle2\" -> \"end\"", dotContent); // Check start executor has special styling - dotContent.Should().Contain("fillcolor=lightgreen"); + Assert.Contains("fillcolor=lightgreen", dotContent); } [Fact] @@ -96,10 +95,10 @@ public void Test_WorkflowViz_Conditional_Edge() var dotContent = workflow.ToDotString(); // Conditional edge should be dashed and labeled - dotContent.Should().Contain("\"start\" -> \"mid\" [style=dashed, label=\"conditional\"];"); + Assert.Contains("\"start\" -> \"mid\" [style=dashed, label=\"conditional\"];", dotContent); // Non-conditional edge should be plain - dotContent.Should().Contain("\"mid\" -> \"end\""); - dotContent.Should().NotContain("\"mid\" -> \"end\" [style=dashed"); + Assert.Contains("\"mid\" -> \"end\"", dotContent); + Assert.DoesNotContain("\"mid\" -> \"end\" [style=dashed", dotContent); } [Fact] @@ -123,25 +122,25 @@ public void Test_WorkflowViz_FanIn_EdgeGroup() var lines = dotContent.Split('\n'); var fanInLines = Array.FindAll(lines, line => line.Contains("shape=ellipse") && line.Contains("label=\"fan-in\"")); - fanInLines.Should().HaveCount(1); + Assert.Single(fanInLines); // Extract the intermediate node id from the line var fanInLine = fanInLines[0]; var firstQuote = fanInLine.IndexOf('"'); var secondQuote = fanInLine.IndexOf('"', firstQuote + 1); - firstQuote.Should().BeGreaterThan(-1); - secondQuote.Should().BeGreaterThan(-1); + Assert.True((firstQuote) > (-1)); + Assert.True((secondQuote) > (-1)); var fanInNodeId = fanInLine.Substring(firstQuote + 1, secondQuote - firstQuote - 1); - fanInNodeId.Should().NotBeNullOrEmpty(); + Assert.False(string.IsNullOrEmpty(fanInNodeId)); // Edges should be routed through the intermediate node, not direct to target - dotContent.Should().Contain($"\"s1\" -> \"{fanInNodeId}\";"); - dotContent.Should().Contain($"\"s2\" -> \"{fanInNodeId}\";"); - dotContent.Should().Contain($"\"{fanInNodeId}\" -> \"t\";"); + Assert.Contains($"\"s1\" -> \"{fanInNodeId}\";", dotContent); + Assert.Contains($"\"s2\" -> \"{fanInNodeId}\";", dotContent); + Assert.Contains($"\"{fanInNodeId}\" -> \"t\";", dotContent); // Ensure direct edges are not present - dotContent.Should().NotContain("\"s1\" -> \"t\""); - dotContent.Should().NotContain("\"s2\" -> \"t\""); + Assert.DoesNotContain("\"s1\" -> \"t\"", dotContent); + Assert.DoesNotContain("\"s2\" -> \"t\"", dotContent); } // Note: Sub-workflow tests are commented out as the current implementation @@ -182,9 +181,9 @@ public void Test_WorkflowViz_FanOut_Edges() var dotContent = workflow.ToDotString(); // Check all fan-out edges are present - dotContent.Should().Contain("\"start\" -> \"target1\""); - dotContent.Should().Contain("\"start\" -> \"target2\""); - dotContent.Should().Contain("\"start\" -> \"target3\""); + Assert.Contains("\"start\" -> \"target1\"", dotContent); + Assert.Contains("\"start\" -> \"target2\"", dotContent); + Assert.Contains("\"start\" -> \"target3\"", dotContent); } [Fact] @@ -208,15 +207,15 @@ public void Test_WorkflowViz_Mixed_EdgeTypes() var dotContent = workflow.ToDotString(); // Check conditional edge - dotContent.Should().Contain("\"start\" -> \"a\" [style=dashed, label=\"conditional\"];"); + Assert.Contains("\"start\" -> \"a\" [style=dashed, label=\"conditional\"];", dotContent); // Check fan-out edges - dotContent.Should().Contain("\"a\" -> \"b\""); - dotContent.Should().Contain("\"a\" -> \"c\""); + Assert.Contains("\"a\" -> \"b\"", dotContent); + Assert.Contains("\"a\" -> \"c\"", dotContent); // Check fan-in (should have intermediate node) - dotContent.Should().Contain("shape=ellipse"); - dotContent.Should().Contain("label=\"fan-in\""); + Assert.Contains("shape=ellipse", dotContent); + Assert.Contains("label=\"fan-in\"", dotContent); } [Fact] @@ -232,9 +231,9 @@ public void Test_WorkflowViz_SingleNode_Workflow() var dotContent = workflow.ToDotString(); // Check single node is present with start styling - dotContent.Should().Contain("\"single\""); - dotContent.Should().Contain("fillcolor=lightgreen"); - dotContent.Should().Contain("(Start)"); + Assert.Contains("\"single\"", dotContent); + Assert.Contains("fillcolor=lightgreen", dotContent); + Assert.Contains("(Start)", dotContent); } [Fact] @@ -252,7 +251,7 @@ public void Test_WorkflowViz_SelfLoop_Edge() var dotContent = workflow.ToDotString(); // Check self-loop edge is present and conditional - dotContent.Should().Contain("\"loop\" -> \"loop\" [style=dashed, label=\"conditional\"];"); + Assert.Contains("\"loop\" -> \"loop\" [style=dashed, label=\"conditional\"];", dotContent); } [Fact] @@ -269,10 +268,10 @@ public void Test_WorkflowViz_ToMermaidString_Basic() var mermaidContent = workflow.ToMermaidString(); // Check that the Mermaid content contains expected elements - mermaidContent.Should().Contain("flowchart TD"); - mermaidContent.Should().Contain("executor1[\"executor1 (Start)\"]"); - mermaidContent.Should().Contain("executor2[\"executor2\"]"); - mermaidContent.Should().Contain("executor1 --> executor2"); + Assert.Contains("flowchart TD", mermaidContent); + Assert.Contains("executor1[\"executor1 (Start)\"]", mermaidContent); + Assert.Contains("executor2[\"executor2\"]", mermaidContent); + Assert.Contains("executor1 --> executor2", mermaidContent); } [Fact] @@ -293,13 +292,13 @@ public void Test_WorkflowViz_Mermaid_Conditional_Edge() var mermaidContent = workflow.ToMermaidString(); // Conditional edge should be dotted with label (using .-> not .-->) - mermaidContent.Should().Contain("-. conditional .-> "); + Assert.Contains("-. conditional .-> ", mermaidContent); // Non-conditional edge should be a specific solid arrow - mermaidContent.Should().Contain("mid --> end"); + Assert.Contains("mid --> end", mermaidContent); // Display labels should be present - mermaidContent.Should().Contain("\"start (Start)\""); - mermaidContent.Should().Contain("\"mid\""); - mermaidContent.Should().Contain("\"end\""); + Assert.Contains("\"start (Start)\"", mermaidContent); + Assert.Contains("\"mid\"", mermaidContent); + Assert.Contains("\"end\"", mermaidContent); } [Fact] @@ -321,27 +320,27 @@ public void Test_WorkflowViz_Mermaid_FanIn_EdgeGroup() // There should be a fan-in node with special styling var lines = mermaidContent.Split('\n'); var fanInLines = Array.FindAll(lines, line => line.Contains("((fan-in))")); - fanInLines.Should().HaveCount(1); + Assert.Single(fanInLines); // Extract the intermediate fan-in node id from the line var fanInLine = fanInLines[0].Trim(); var fanInNodeId = fanInLine.Substring(0, fanInLine.IndexOf("((fan-in))", StringComparison.Ordinal)).Trim(); - fanInNodeId.Should().NotBeNullOrEmpty(); + Assert.False(string.IsNullOrEmpty(fanInNodeId)); // Edges should be routed through the intermediate node - mermaidContent.Should().Contain($"s1 --> {fanInNodeId}"); - mermaidContent.Should().Contain($"s2 --> {fanInNodeId}"); - mermaidContent.Should().Contain($"{fanInNodeId} --> t"); + Assert.Contains($"s1 --> {fanInNodeId}", mermaidContent); + Assert.Contains($"s2 --> {fanInNodeId}", mermaidContent); + Assert.Contains($"{fanInNodeId} --> t", mermaidContent); // Ensure direct edges are not present - mermaidContent.Should().NotContain("s1 --> t"); - mermaidContent.Should().NotContain("s2 --> t"); + Assert.DoesNotContain("s1 --> t", mermaidContent); + Assert.DoesNotContain("s2 --> t", mermaidContent); // Display labels should be present - mermaidContent.Should().Contain("\"start (Start)\""); - mermaidContent.Should().Contain("\"s1\""); - mermaidContent.Should().Contain("\"s2\""); - mermaidContent.Should().Contain("\"t\""); + Assert.Contains("\"start (Start)\"", mermaidContent); + Assert.Contains("\"s1\"", mermaidContent); + Assert.Contains("\"s2\"", mermaidContent); + Assert.Contains("\"t\"", mermaidContent); // All node IDs should be safe aliases (ASCII-only identifiers) foreach (var line in mermaidContent.Split('\n')) @@ -351,7 +350,7 @@ public void Test_WorkflowViz_Mermaid_FanIn_EdgeGroup() { var bracketIdx = trimmed.IndexOfAny(['[', '(']); var nodeId = trimmed.Substring(0, bracketIdx); - nodeId.Should().MatchRegex("^[a-zA-Z_][a-zA-Z0-9_]*$"); + Assert.Matches("^[a-zA-Z_][a-zA-Z0-9_]*$", nodeId); } } } @@ -375,17 +374,17 @@ public void Test_WorkflowViz_Mermaid_Complex_Workflow() var mermaidContent = workflow.ToMermaidString(); // Check display labels are present - mermaidContent.Should().Contain("\"start (Start)\""); - mermaidContent.Should().Contain("\"middle1\""); - mermaidContent.Should().Contain("\"middle2\""); - mermaidContent.Should().Contain("\"end\""); + Assert.Contains("\"start (Start)\"", mermaidContent); + Assert.Contains("\"middle1\"", mermaidContent); + Assert.Contains("\"middle2\"", mermaidContent); + Assert.Contains("\"end\"", mermaidContent); // Check that sanitized IDs are used and all edges connect them - mermaidContent.Should().Contain("start[\"start (Start)\"]"); - mermaidContent.Should().Contain("start --> middle1"); - mermaidContent.Should().Contain("start --> middle2"); - mermaidContent.Should().Contain("middle1 --> end"); - mermaidContent.Should().Contain("middle2 --> end"); + Assert.Contains("start[\"start (Start)\"]", mermaidContent); + Assert.Contains("start --> middle1", mermaidContent); + Assert.Contains("start --> middle2", mermaidContent); + Assert.Contains("middle1 --> end", mermaidContent); + Assert.Contains("middle2 --> end", mermaidContent); } [Fact] @@ -409,18 +408,18 @@ public void Test_WorkflowViz_Mermaid_Mixed_EdgeTypes() var mermaidContent = workflow.ToMermaidString(); // Check conditional edge uses correct syntax (.-> not .-->) - mermaidContent.Should().Contain("-. conditional .->"); - mermaidContent.Should().NotContain(".-->"); + Assert.Contains("-. conditional .->", mermaidContent); + Assert.DoesNotContain(".-->", mermaidContent); // Check fan-in (should have intermediate node) - mermaidContent.Should().Contain("((fan-in))"); + Assert.Contains("((fan-in))", mermaidContent); // Display labels should be present - mermaidContent.Should().Contain("\"start (Start)\""); - mermaidContent.Should().Contain("\"a\""); - mermaidContent.Should().Contain("\"b\""); - mermaidContent.Should().Contain("\"c\""); - mermaidContent.Should().Contain("\"end\""); + Assert.Contains("\"start (Start)\"", mermaidContent); + Assert.Contains("\"a\"", mermaidContent); + Assert.Contains("\"b\"", mermaidContent); + Assert.Contains("\"c\"", mermaidContent); + Assert.Contains("\"end\"", mermaidContent); } [Fact] @@ -437,9 +436,9 @@ public void Test_WorkflowViz_Mermaid_Edge_Label_With_Pipe() var mermaidContent = workflow.ToMermaidString(); // Should escape pipe character - mermaidContent.Should().Contain("-->|High | Low Priority|"); + Assert.Contains("-->|High | Low Priority|", mermaidContent); // Should not contain unescaped pipe that would break syntax - mermaidContent.Should().NotContain("-->|High | Low"); + Assert.DoesNotContain("-->|High | Low", mermaidContent); } [Fact] @@ -456,9 +455,9 @@ public void Test_WorkflowViz_Mermaid_Edge_Label_With_Special_Chars() var mermaidContent = workflow.ToMermaidString(); // Should escape special characters - mermaidContent.Should().Contain("&"); - mermaidContent.Should().Contain(">"); - mermaidContent.Should().Contain("<"); + Assert.Contains("&", mermaidContent); + Assert.Contains(">", mermaidContent); + Assert.Contains("<", mermaidContent); } [Fact] @@ -475,9 +474,9 @@ public void Test_WorkflowViz_Mermaid_Edge_Label_With_Newline() var mermaidContent = workflow.ToMermaidString(); // Should convert newline to
- mermaidContent.Should().Contain("Line 1
Line 2"); + Assert.Contains("Line 1
Line 2", mermaidContent); // Should not contain literal newline in the label (but the overall output has newlines between statements) - mermaidContent.Should().NotContain("Line 1\nLine 2"); + Assert.DoesNotContain("Line 1\nLine 2", mermaidContent); } [Fact] @@ -497,8 +496,8 @@ public void Test_WorkflowViz_Mermaid_ConditionalEdge_ArrowSyntax() var mermaidContent = workflow.ToMermaidString(); // The output should use ".->" not ".-->" for conditional (dotted) edges - mermaidContent.Should().NotContain(".-->", because: "'.-->' is invalid Mermaid syntax for dotted arrows; should be '.->'"); - mermaidContent.Should().Contain("-. conditional .->", because: "'-. label .->' is the correct Mermaid syntax for dotted arrows with labels"); + Assert.DoesNotContain(".-->", mermaidContent); + Assert.Contains("-. conditional .->", mermaidContent); } [Fact] @@ -527,7 +526,7 @@ public void Test_WorkflowViz_Mermaid_IdentifiersWithSpaces() { var bracketIdx = trimmed.IndexOf('['); var nodeId = trimmed.Substring(0, bracketIdx); - nodeId.Should().NotContain(" ", because: $"Mermaid node IDs must not contain spaces, but got '{nodeId}'"); + Assert.DoesNotContain(" ", nodeId); } } } @@ -546,8 +545,8 @@ public void Test_WorkflowViz_Mermaid_IdentifiersWithUnicode() var mermaidContent = workflow.ToMermaidString(); // The display labels should contain the original names - mermaidContent.Should().Contain("ユーザー入力"); - mermaidContent.Should().Contain("データ処理"); + Assert.Contains("ユーザー入力", mermaidContent); + Assert.Contains("データ処理", mermaidContent); // But node IDs (before the bracket) should be safe ASCII-only identifiers foreach (var line in mermaidContent.Split('\n')) @@ -558,8 +557,7 @@ public void Test_WorkflowViz_Mermaid_IdentifiersWithUnicode() var bracketIdx = trimmed.IndexOf('['); var nodeId = trimmed.Substring(0, bracketIdx); // Node ID should start with a letter or underscore, followed by ASCII alphanumeric or underscores - nodeId.Should().MatchRegex("^[a-zA-Z_][a-zA-Z0-9_]*$", - because: $"Mermaid node IDs should be ASCII-safe, but got '{nodeId}'"); + Assert.Matches("^[a-zA-Z_][a-zA-Z0-9_]*$", nodeId); } } } From 68bcd4d125c0dc94e6d02b8a3a8e8b8fbb63d544 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 28 Aug 2026 13:45:06 -0400 Subject: [PATCH 2/7] chore: formatting --- .../SharedStateTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/SharedStateTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/SharedStateTests.cs index d0244f7bf03..079ab79d1d1 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/SharedStateTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/SharedStateTests.cs @@ -85,7 +85,7 @@ public async Task StateSnapshot_UpdateHasAssistantRoleAndNoConversationIdAsync() Assert.NotNull(stateUpdate); ChatResponseUpdate chatUpdate = stateUpdate!.AsChatResponseUpdate(); - Assert.True((chatUpdate.RawRepresentation) is StateSnapshotEvent); + Assert.True(chatUpdate.RawRepresentation is StateSnapshotEvent); Assert.Null(chatUpdate.ConversationId); Assert.Equal(ChatRole.Assistant, chatUpdate.Role); } From 2062e072b0374b2611f97a4795ccd2527cc7244e Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 28 Aug 2026 13:50:47 -0400 Subject: [PATCH 3/7] chore: formatting --- .../ConfigureAGUIJsonOptionsTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests/ConfigureAGUIJsonOptionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests/ConfigureAGUIJsonOptionsTests.cs index 4adbf485a3e..88ada833871 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests/ConfigureAGUIJsonOptionsTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests/ConfigureAGUIJsonOptionsTests.cs @@ -20,7 +20,7 @@ public void AddAGUIServer_ConfiguresJsonOptions_ResolvesAGUIWireTypes() // The AG-UI wire context must be in the resolver chain (needed on the net10 // TypedResults.ServerSentEvents path, which serializes events through these options). - Assert.Null(Record.Exception(()=>options.GetTypeInfo(typeof(RunStartedEvent)))); + Assert.Null(Record.Exception(() => options.GetTypeInfo(typeof(RunStartedEvent)))); } [Fact] @@ -29,7 +29,7 @@ public void AddAGUIServer_ConfiguresJsonOptions_ResolvesAgentAbstractionsTypes() JsonSerializerOptions options = BuildConfiguredSerializerOptions(); // The Agent Framework abstractions resolver must also be present so M.E.AI types resolve. - Assert.Null(Record.Exception(()=>options.GetTypeInfo(typeof(ChatMessage)))); + Assert.Null(Record.Exception(() => options.GetTypeInfo(typeof(ChatMessage)))); } private static JsonSerializerOptions BuildConfiguredSerializerOptions() From 24d50fb0a6c2f5743bd9b348f49277faa73441f2 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 28 Aug 2026 13:54:25 -0400 Subject: [PATCH 4/7] chore: formatting --- .../ListAgentToolsWithTasksTests.cs | 32 ++++++------ .../TaskAwareMcpClientAIFunctionTests.cs | 52 +++++++++---------- 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/dotnet/tests/Microsoft.Agents.AI.Mcp.UnitTests/ListAgentToolsWithTasksTests.cs b/dotnet/tests/Microsoft.Agents.AI.Mcp.UnitTests/ListAgentToolsWithTasksTests.cs index 5a407460a67..9cb92dbc435 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Mcp.UnitTests/ListAgentToolsWithTasksTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Mcp.UnitTests/ListAgentToolsWithTasksTests.cs @@ -35,10 +35,10 @@ public async Task ListAgentToolsWithTasks_ThrowsOnNullClientAsync() ModelContextProtocol.Client.McpClient client = null!; // Act - Func act = async () => await client.ListAgentToolsWithTasksAsync(); + async Task actAsync() => await client.ListAgentToolsWithTasksAsync(); // Assert - await Assert.ThrowsAsync(act); + await Assert.ThrowsAsync(actAsync); } [Fact] @@ -52,10 +52,10 @@ public async Task ListAgentToolsWithTasks_NonPositiveStuckPollLimit_ThrowsAsync( var options = new McpTaskOptions { MaxConsecutiveStuckPolls = 0 }; // Act - Func act = async () => await fixture.Client.ListAgentToolsWithTasksAsync(options); + async Task actAsync() => await fixture.Client.ListAgentToolsWithTasksAsync(options); // Assert - await Assert.ThrowsAsync(act); + await Assert.ThrowsAsync(actAsync); } [Fact] @@ -69,10 +69,10 @@ public async Task ListAgentToolsWithTasks_NonPositiveInputRequestLimit_ThrowsAsy var options = new McpTaskOptions { MaxTotalInputRequests = 0 }; // Act - Func act = async () => await fixture.Client.ListAgentToolsWithTasksAsync(options); + async Task actAsync() => await fixture.Client.ListAgentToolsWithTasksAsync(options); // Assert - await Assert.ThrowsAsync(act); + await Assert.ThrowsAsync(actAsync); } [Fact] @@ -86,10 +86,10 @@ public async Task ListAgentToolsWithTasks_NonPositiveCancellationTimeout_ThrowsA var options = new McpTaskOptions { RemoteCancellationTimeout = TimeSpan.Zero }; // Act - Func act = async () => await fixture.Client.ListAgentToolsWithTasksAsync(options); + async Task actAsync() => await fixture.Client.ListAgentToolsWithTasksAsync(options); // Assert - await Assert.ThrowsAsync(act); + await Assert.ThrowsAsync(actAsync); } [Fact] @@ -103,10 +103,10 @@ public async Task ListAgentToolsWithTasks_SubMillisecondCancellationTimeout_Thro var options = new McpTaskOptions { RemoteCancellationTimeout = TimeSpan.FromTicks(1) }; // Act - Func act = async () => await fixture.Client.ListAgentToolsWithTasksAsync(options); + async Task actAsync() => await fixture.Client.ListAgentToolsWithTasksAsync(options); // Assert - await Assert.ThrowsAsync(act); + await Assert.ThrowsAsync(actAsync); } [Fact] @@ -124,10 +124,10 @@ public async Task ListAgentToolsWithTasks_InvalidPollingIntervalRange_ThrowsAsyn }; // Act - Func act = async () => await fixture.Client.ListAgentToolsWithTasksAsync(options); + async Task actAsync() => await fixture.Client.ListAgentToolsWithTasksAsync(options); // Assert - await Assert.ThrowsAsync(act); + await Assert.ThrowsAsync(actAsync); } [Fact] @@ -145,10 +145,10 @@ public async Task ListAgentToolsWithTasks_PollingRangeWithoutWholeMillisecond_Th }; // Act - Func act = async () => await fixture.Client.ListAgentToolsWithTasksAsync(options); + async Task actAsync() => await fixture.Client.ListAgentToolsWithTasksAsync(options); // Assert - await Assert.ThrowsAsync(act); + await Assert.ThrowsAsync(actAsync); } [Fact] @@ -165,9 +165,9 @@ public async Task ListAgentToolsWithTasks_PollingMaximumAboveRuntimeLimit_Throws }; // Act - Func act = async () => await fixture.Client.ListAgentToolsWithTasksAsync(options); + async Task actAsync() => await fixture.Client.ListAgentToolsWithTasksAsync(options); // Assert - await Assert.ThrowsAsync(act); + await Assert.ThrowsAsync(actAsync); } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Mcp.UnitTests/TaskAwareMcpClientAIFunctionTests.cs b/dotnet/tests/Microsoft.Agents.AI.Mcp.UnitTests/TaskAwareMcpClientAIFunctionTests.cs index c7f867fa82a..9e101a899a0 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Mcp.UnitTests/TaskAwareMcpClientAIFunctionTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Mcp.UnitTests/TaskAwareMcpClientAIFunctionTests.cs @@ -35,7 +35,7 @@ public async Task InvokeAsync_TaskBackedTool_ReturnsResultAsync() // Assert Assert.Equal("task-result", Assert.IsType(result).Text); Assert.Equal(1, fixture.CreatedTaskCount); - Assert.True((fixture.PollCount) > (0)); + Assert.True(fixture.PollCount > 0); } [Theory] @@ -60,10 +60,10 @@ public async Task InvokeAsync_InvalidInitialPollInterval_CancelsRemoteTaskAsync( AIFunction wrapped = (await fixture.Client.ListAgentToolsWithTasksAsync()).Single(); // Act - Func act = async () => await wrapped.InvokeAsync(arguments: null, CancellationToken.None); + async Task actAsync() => await wrapped.InvokeAsync(arguments: null, CancellationToken.None); // Assert - ModelContextProtocol.McpException exception = await Assert.ThrowsAsync(act); + ModelContextProtocol.McpException exception = await Assert.ThrowsAsync(actAsync); Assert.Contains($"pollIntervalMs of {pollIntervalMs}", exception.Message); await fixture.RemoteCancellationObserved.WaitAsync(TimeSpan.FromSeconds(5)); Assert.Equal(1, fixture.SuccessfulCancellationTransitionCount); @@ -89,10 +89,10 @@ public async Task InvokeAsync_InvalidUpdatedPollInterval_CancelsRemoteTaskAsync( AIFunction wrapped = (await fixture.Client.ListAgentToolsWithTasksAsync()).Single(); // Act - Func act = async () => await wrapped.InvokeAsync(arguments: null, CancellationToken.None); + async Task actAsync() => await wrapped.InvokeAsync(arguments: null, CancellationToken.None); // Assert - ModelContextProtocol.McpException exception = await Assert.ThrowsAsync(act); + ModelContextProtocol.McpException exception = await Assert.ThrowsAsync(actAsync); Assert.Contains("pollIntervalMs of 0", exception.Message); await fixture.RemoteCancellationObserved.WaitAsync(TimeSpan.FromSeconds(5)); Assert.Equal(1, fixture.SuccessfulCancellationTransitionCount); @@ -355,10 +355,10 @@ await fixture.FailLatestTaskAsync( JsonSerializer.SerializeToElement(new { code = -32603, message = "simulated failure" })); // Act - Func act = async () => await invocation; + async Task actAsync() => await invocation; // Assert - ModelContextProtocol.McpException exception = await Assert.ThrowsAsync(act); + ModelContextProtocol.McpException exception = await Assert.ThrowsAsync(actAsync); Assert.Contains("simulated failure", exception.Message); Assert.Equal(0, fixture.SuccessfulCancellationTransitionCount); Assert.Equal(0, fixture.CancellationRequestCount); @@ -393,10 +393,10 @@ public async Task InvokeAsync_ServerCancelledTask_ThrowsOperationCanceledAsync() await fixture.CancelLatestTaskAsync(); // Act - Func act = async () => await invocation; + async Task actAsync() => await invocation; // Assert - OperationCanceledException exception = await Assert.ThrowsAsync(act); + OperationCanceledException exception = await Assert.ThrowsAsync(actAsync); Assert.Contains("cancelled by the server", exception.Message); Assert.Equal(1, fixture.SuccessfulCancellationTransitionCount); Assert.Equal(0, fixture.CancellationRequestCount); @@ -439,10 +439,10 @@ public async Task InvokeAsync_InputHandlerFailure_CancelsRemoteTaskAsync() AIFunction wrapped = (await fixture.Client.ListAgentToolsWithTasksAsync()).Single(); // Act - Func act = async () => await wrapped.InvokeAsync(arguments: null, CancellationToken.None); + async Task actAsync() => await wrapped.InvokeAsync(arguments: null, CancellationToken.None); // Assert - InvalidOperationException exception = await Assert.ThrowsAsync(act); + InvalidOperationException exception = await Assert.ThrowsAsync(actAsync); Assert.Equal("input handler failed", exception.Message); await fixture.RemoteCancellationObserved.WaitAsync(TimeSpan.FromSeconds(5)); Assert.Equal(1, fixture.SuccessfulCancellationTransitionCount); @@ -468,10 +468,10 @@ public async Task InvokeAsync_GetTaskFailure_CancelsRemoteTaskAndPreservesProtoc AIFunction wrapped = (await fixture.Client.ListAgentToolsWithTasksAsync()).Single(); // Act - Func act = async () => await wrapped.InvokeAsync(arguments: null, CancellationToken.None); + async Task actAsync() => await wrapped.InvokeAsync(arguments: null, CancellationToken.None); // Assert - ModelContextProtocol.McpProtocolException exception = await Assert.ThrowsAsync(act); + ModelContextProtocol.McpProtocolException exception = await Assert.ThrowsAsync(actAsync); Assert.Equal("Request failed (remote): An error occurred.", exception.Message); await fixture.RemoteCancellationObserved.WaitAsync(TimeSpan.FromSeconds(5)); Assert.Equal(1, fixture.SuccessfulCancellationTransitionCount); @@ -511,10 +511,10 @@ public async Task InvokeAsync_UpdateTaskFailure_CancelsRemoteTaskAndPreservesPro AIFunction wrapped = (await fixture.Client.ListAgentToolsWithTasksAsync()).Single(); // Act - Func act = async () => await wrapped.InvokeAsync(arguments: null, CancellationToken.None); + async Task actAsync() => await wrapped.InvokeAsync(arguments: null, CancellationToken.None); // Assert - ModelContextProtocol.McpProtocolException exception = await Assert.ThrowsAsync(act); + ModelContextProtocol.McpProtocolException exception = await Assert.ThrowsAsync(actAsync); Assert.Equal("Request failed (remote): An error occurred.", exception.Message); await fixture.RemoteCancellationObserved.WaitAsync(TimeSpan.FromSeconds(5)); Assert.Equal(1, fixture.SuccessfulCancellationTransitionCount); @@ -545,10 +545,10 @@ public async Task InvokeAsync_MalformedCompletedResult_DoesNotCancelTerminalTask await fixture.CompleteLatestTaskAsync(JsonSerializer.SerializeToElement("malformed")); // Act - Func act = async () => await invocation; + async Task actAsync() => await invocation; // Assert - await Assert.ThrowsAsync(act); + await Assert.ThrowsAsync(actAsync); Assert.Equal(0, fixture.SuccessfulCancellationTransitionCount); Assert.Equal(0, fixture.CancellationRequestCount); } @@ -592,10 +592,10 @@ public async Task InvokeAsync_StuckInputRequired_CancelsRemoteTaskAsync() AIFunction wrapped = (await fixture.Client.ListAgentToolsWithTasksAsync(options)).Single(); // Act - Func act = async () => await wrapped.InvokeAsync(arguments: null, CancellationToken.None); + async Task actAsync() => await wrapped.InvokeAsync(arguments: null, CancellationToken.None); // Assert - ModelContextProtocol.McpException exception = await Assert.ThrowsAsync(act); + ModelContextProtocol.McpException exception = await Assert.ThrowsAsync(actAsync); Assert.Contains("2 consecutive polls", exception.Message); await fixture.RemoteCancellationObserved.WaitAsync(TimeSpan.FromSeconds(5)); Assert.Equal(1, fixture.SuccessfulCancellationTransitionCount); @@ -693,10 +693,10 @@ public async Task InvokeAsync_InputRequestLimitExceeded_CancelsBeforeDispatchAsy AIFunction wrapped = (await fixture.Client.ListAgentToolsWithTasksAsync(options)).Single(); // Act - Func act = async () => await wrapped.InvokeAsync(arguments: null, CancellationToken.None); + async Task actAsync() => await wrapped.InvokeAsync(arguments: null, CancellationToken.None); // Assert - ModelContextProtocol.McpException exception = await Assert.ThrowsAsync(act); + ModelContextProtocol.McpException exception = await Assert.ThrowsAsync(actAsync); Assert.Contains("limit of 2 unique input requests", exception.Message); Assert.Equal(2, handledInputRequests); await fixture.RemoteCancellationObserved.WaitAsync(TimeSpan.FromSeconds(5)); @@ -735,14 +735,14 @@ public async Task InvokeAsync_LocalCancellation_CancelsRemoteTaskAsync() // Act cts.Cancel(); - Func act = async () => await invocation; + async Task actAsync() => await invocation; // Assert - await Assert.ThrowsAnyAsync(act); + await Assert.ThrowsAnyAsync(actAsync); await fixture.RemoteCancellationObserved.WaitAsync(TimeSpan.FromSeconds(5)); await serverCancelled.Task.WaitAsync(TimeSpan.FromSeconds(5)); Assert.Equal(1, fixture.CreatedTaskCount); - Assert.True((fixture.PollCount) > (0)); + Assert.True(fixture.PollCount > 0); Assert.Equal(1, fixture.SuccessfulCancellationTransitionCount); Assert.Equal(1, fixture.CancellationRequestCount); } @@ -773,10 +773,10 @@ public async Task InvokeAsync_LocalCancellation_DoesNotCancelRemoteTaskWhenDisab // Act cts.Cancel(); - Func act = async () => await invocation; + async Task actAsync() => await invocation; // Assert - await Assert.ThrowsAnyAsync(act); + await Assert.ThrowsAnyAsync(actAsync); Assert.Equal(0, fixture.SuccessfulCancellationTransitionCount); Assert.Equal(0, fixture.CancellationRequestCount); } From efc0c5f25e0cce25fafde620e89f3dea6ae959bd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 28 Aug 2026 18:02:34 +0000 Subject: [PATCH 5/7] fix: preserve xunit assertion coverage Co-authored-by: baywet <7905502+baywet@users.noreply.github.com> --- .../Kit/PortableValuePredicateTests.cs | 4 +- .../AIAgentHostExecutorTests.cs | 3 +- .../ChatForwardingExecutorTests.cs | 6 +-- .../ChatProtocolExecutorTests.cs | 2 +- .../EdgeMapSmokeTests.cs | 10 ++-- .../FunctionExecutorTests.cs | 13 ++--- .../HandoffMessageFilterTests.cs | 50 +++++++++++++++---- .../HandoffOrchestrationTests.cs | 10 +++- .../PolymorphicOutputTests.cs | 10 ++-- .../RouteBuilderTests.cs | 8 +-- .../SampleSmokeTest.cs | 2 +- .../StateKeyObjectTests.cs | 4 +- 12 files changed, 81 insertions(+), 41 deletions(-) diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Kit/PortableValuePredicateTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Kit/PortableValuePredicateTests.cs index 9c4058835d4..d63fa49f9e6 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Kit/PortableValuePredicateTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Kit/PortableValuePredicateTests.cs @@ -1,4 +1,6 @@ -using Microsoft.Agents.AI.Workflows.Declarative.Events; +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.Agents.AI.Workflows.Declarative.Events; using Microsoft.Agents.AI.Workflows.Declarative.Kit; using Microsoft.Agents.AI.Workflows.Declarative.ObjectModel; diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AIAgentHostExecutorTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AIAgentHostExecutorTests.cs index 74a8474e65e..2be0f4aec15 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AIAgentHostExecutorTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AIAgentHostExecutorTests.cs @@ -87,7 +87,7 @@ public async Task Test_AgentHostExecutor_AssignsStableMessageIdToContentfulStrea // Assert AgentResponseUpdateEvent[] updateEvents = testContext.Events.OfType().ToArray(); Assert.Equal(3, updateEvents.Length); - Assert.True(string.IsNullOrEmpty(updateEvents[0].Update.MessageId)); + Assert.Equal(string.Empty, updateEvents[0].Update.MessageId); string? messageId = updateEvents[1].Update.MessageId; Assert.False(string.IsNullOrEmpty(messageId)); @@ -271,7 +271,6 @@ List ExtractAndValidateRequestContents() where TRequest : AICo // Assert 2 // Since we are not finished, we expect the agent to not have produced a final response (="Remaining: 1") List agentResponseEvents = testContext.Events.OfType().ToList(); - agentResponseEvents = testContext.Events.OfType().ToList(); Assert.NotEmpty(agentResponseEvents); AgentResponseEvent lastResponseEvent = agentResponseEvents.Last(); diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/ChatForwardingExecutorTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/ChatForwardingExecutorTests.cs index 65728f5becc..c2dcdd906bd 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/ChatForwardingExecutorTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/ChatForwardingExecutorTests.cs @@ -113,7 +113,7 @@ public async Task Test_ChatForwardingExecutor_ForwardsChatMessageUnmodifiedAsync TestWorkflowContext testContext = await this.RunForwardMessageTestAsync(executor, testMessage); // Assert - Assert.Single(testContext.SentMessages); + Assert.Same(testMessage, Assert.Single(testContext.SentMessages)); } [Theory] @@ -133,7 +133,7 @@ TestWorkflowContext testContext : await this.RunForwardMessageTestAsync(executor, testMessages); // Assert - Assert.Single(testContext.SentMessages); + Assert.Same(testMessages, Assert.Single(testContext.SentMessages)); } [Fact] @@ -148,7 +148,7 @@ public async Task Test_ChatForwardingExecutor_ForwardsChatMessageArrayUnchangedA TestWorkflowContext testContext = await this.RunForwardMessageTestAsync(executor, testMessages); // Assert - Assert.Single(testContext.SentMessages); + Assert.Same(testMessages, Assert.Single(testContext.SentMessages)); } [Fact] diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/ChatProtocolExecutorTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/ChatProtocolExecutorTests.cs index 1fbbed541ed..85170ad6b26 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/ChatProtocolExecutorTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/ChatProtocolExecutorTests.cs @@ -137,7 +137,7 @@ await executor.ExecuteCoreAsync(new List await executor.TakeTurnAsync(new TurnToken(emitEvents: false), context); Assert.Equal(4, executor.ReceivedMessages.Count); - Assert.Equal("Message 1", executor.ReceivedMessages[0].Text); + Assert.Equal(["Message 1", "Message 2", "Message 3", "Message 4"], executor.ReceivedMessages.Select(m => m.Text)); Assert.Equal(1, executor.TurnCount); executor.ReceivedMessages.Clear(); diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/EdgeMapSmokeTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/EdgeMapSmokeTests.cs index 1986b7beec3..18cdd005bca 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/EdgeMapSmokeTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/EdgeMapSmokeTests.cs @@ -29,8 +29,9 @@ public async Task Test_EdgeMap_RoutesStaticPortAsync() Assert.NotNull(mapping); List deliveries = mapping.Deliveries.ToList(); - Assert.All(deliveries, delivery => Assert.Equal(executor.Id, delivery.TargetId)); - Assert.Equal(responseMessage, deliveries[0].Envelope.Message); + MessageDelivery delivery = Assert.Single(deliveries); + Assert.Equal(executor.Id, delivery.TargetId); + Assert.Equal(responseMessage, delivery.Envelope.Message); } [Fact] @@ -55,8 +56,9 @@ async ValueTask RunPortTestAsync(string portId) Assert.NotNull(mapping); List deliveries = mapping.Deliveries.ToList(); - Assert.All(deliveries, delivery => Assert.Equal(executor.Id, delivery.TargetId)); - Assert.Equal(responseMessage, deliveries[0].Envelope.Message); + MessageDelivery delivery = Assert.Single(deliveries); + Assert.Equal(executor.Id, delivery.TargetId); + Assert.Equal(responseMessage, delivery.Envelope.Message); } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/FunctionExecutorTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/FunctionExecutorTests.cs index cbb247cb07a..c4737acebbc 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/FunctionExecutorTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/FunctionExecutorTests.cs @@ -57,7 +57,7 @@ internal static void CheckInvoked(ExecutorTestResult result, TMessage Assert.Contains(result.Context.EmittedEvents, evt => evt is ExecutorInvokedEvent invoked && MatchesExpected(invoked.Data, expectedInput)); Assert.Contains(result.Context.EmittedEvents, evt => evt is ExecutorCompletedEvent completed - && MatchesExpected(completed.Data, expectedCallResult)); + && ReferenceEquals(completed.Data, result.CallResult)); } internal static void CheckInvoked(ExecutorTestResult result, TMessage expectedInput, TOutput expectedCallResult) @@ -69,16 +69,13 @@ internal static void CheckInvoked(ExecutorTestResult result, Assert.Contains(result.Context.EmittedEvents, evt => evt is ExecutorInvokedEvent invoked && MatchesExpected(invoked.Data, expectedInput)); Assert.Contains(result.Context.EmittedEvents, evt => evt is ExecutorCompletedEvent completed - && MatchesExpected(completed.Data, expectedCallResult)); + && ReferenceEquals(completed.Data, result.CallResult)); } - private static bool MatchesExpected(object? actual, object? expected) + private static bool MatchesExpected(object? actual, TExpected expected) + where TExpected : class { - object? normalizedActual = actual is PortableValue portableValue && expected is not null - ? portableValue.AsType(expected.GetType()) - : actual; - - return Equals(expected, normalizedActual); + return actual as TExpected == expected; } internal TestWorkflowContext CreateWorkflowContext(Executor executor) => new(executor.Id); diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/HandoffMessageFilterTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/HandoffMessageFilterTests.cs index 510ebd26f63..43e48fff1ce 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/HandoffMessageFilterTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/HandoffMessageFilterTests.cs @@ -1,6 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. -using System; using System.Collections.Generic; using Microsoft.Agents.AI.Workflows.Specialized; using Microsoft.Extensions.AI; @@ -11,18 +10,18 @@ public class HandoffMessageFilterTests { private List CreateTestMessages(bool firstAgentUsesCallId, bool secondAgentUsesCallId, HandoffToolCallFilteringBehavior filter = HandoffToolCallFilteringBehavior.None) { - FunctionCallContent handoffRequest1 = CreateHandoffCall(1, firstAgentUsesCallId); + FunctionCallContent handoffRequest1 = CreateHandoffCall(1, firstAgentUsesCallId, "first"); FunctionResultContent handoffResponse1 = CreateHandoffResponse(handoffRequest1); - FunctionCallContent toolCall = CreateToolCall(secondAgentUsesCallId); + FunctionCallContent toolCall = CreateToolCall(secondAgentUsesCallId, "tool"); FunctionResultContent toolResponse = CreateToolResponse(toolCall); // Approvals come from the function call middleware over ChatClient, so we can expect there to be a RequestId (not that we // care, because we do not filter approval content) - ToolApprovalRequestContent toolApproval = new(Guid.NewGuid().ToString("N"), toolCall); + ToolApprovalRequestContent toolApproval = new("approval_request", toolCall); ToolApprovalResponseContent toolApprovalResponse = new(toolApproval.RequestId, true, toolCall); - FunctionCallContent handoffRequest2 = CreateHandoffCall(1, secondAgentUsesCallId); + FunctionCallContent handoffRequest2 = CreateHandoffCall(1, secondAgentUsesCallId, "second"); FunctionResultContent handoffResponse2 = CreateHandoffResponse(handoffRequest2); List result = [new(ChatRole.User, "Hello")]; @@ -62,10 +61,10 @@ private List CreateTestMessages(bool firstAgentUsesCallId, bool sec return result; } - private static FunctionCallContent CreateHandoffCall(int id, bool useCallId) + private static FunctionCallContent CreateHandoffCall(int id, bool useCallId, string callIdSuffix) { string callName = $"{HandoffWorkflowBuilder.FunctionPrefix}{id}"; - string callId = useCallId ? Guid.NewGuid().ToString("N") : callName; + string callId = useCallId ? $"{callName}_{callIdSuffix}" : callName; return new FunctionCallContent(callId, callName); } @@ -73,10 +72,10 @@ private static FunctionCallContent CreateHandoffCall(int id, bool useCallId) private static FunctionResultContent CreateHandoffResponse(FunctionCallContent call) => HandoffAgentExecutor.CreateHandoffResult(call.CallId); - private static FunctionCallContent CreateToolCall(bool useCallId) + private static FunctionCallContent CreateToolCall(bool useCallId, string callIdSuffix) { const string CallName = "ToolFunction"; - string callId = useCallId ? Guid.NewGuid().ToString("N") : CallName; + string callId = useCallId ? $"{CallName}_{callIdSuffix}" : CallName; return new FunctionCallContent(callId, CallName); } @@ -130,8 +129,39 @@ private static void AssertMessageShape(ChatMessage expected, ChatMessage actual) if (expectedContent is FunctionCallContent expectedCall && actualContent is FunctionCallContent actualCall) { - Assert.Equal(expectedCall.Name, actualCall.Name); + AssertFunctionCallContent(expectedCall, actualCall); + } + else if (expectedContent is FunctionResultContent expectedResult && actualContent is FunctionResultContent actualResult) + { + Assert.Equal(expectedResult.CallId, actualResult.CallId); + Assert.Equivalent(expectedResult.Result, actualResult.Result); + } + else if (expectedContent is ToolApprovalRequestContent expectedApprovalRequest && actualContent is ToolApprovalRequestContent actualApprovalRequest) + { + Assert.Equal(expectedApprovalRequest.RequestId, actualApprovalRequest.RequestId); + AssertFunctionCallContent( + Assert.IsType(expectedApprovalRequest.ToolCall), + Assert.IsType(actualApprovalRequest.ToolCall)); + } + else if (expectedContent is ToolApprovalResponseContent expectedApprovalResponse && actualContent is ToolApprovalResponseContent actualApprovalResponse) + { + Assert.Equal(expectedApprovalResponse.RequestId, actualApprovalResponse.RequestId); + Assert.Equal(expectedApprovalResponse.Approved, actualApprovalResponse.Approved); + AssertFunctionCallContent( + Assert.IsType(expectedApprovalResponse.ToolCall), + Assert.IsType(actualApprovalResponse.ToolCall)); + } + else + { + Assert.Equivalent(expectedContent, actualContent); } } } + + private static void AssertFunctionCallContent(FunctionCallContent expected, FunctionCallContent actual) + { + Assert.Equal(expected.CallId, actual.CallId); + Assert.Equal(expected.Name, actual.Name); + Assert.Equivalent(expected.Arguments, actual.Arguments); + } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/HandoffOrchestrationTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/HandoffOrchestrationTests.cs index 82feb46a303..2632d706b9e 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/HandoffOrchestrationTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/HandoffOrchestrationTests.cs @@ -402,7 +402,15 @@ public async Task Handoffs_ReturnToInitialAgent_AsAgentKeepsInvocationsSeparateA // Assert Assert.Equal(2, initialAgentInvocationCount); - Assert.Single(GetMessageSequence(response.Messages), message => message == "call:call1"); + Assert.Equal( + [ + "call:call1", + "result:call1", + "call:call2", + "result:call2", + "text:Final response", + ], + GetMessageSequence(response.Messages)); } [Fact] diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/PolymorphicOutputTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/PolymorphicOutputTests.cs index f0fd0d40961..c9d2b990b16 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/PolymorphicOutputTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/PolymorphicOutputTests.cs @@ -166,8 +166,8 @@ public async Task ReturningDerivedType_WhenBaseTypeIsDeclared_ShouldSucceedAsync Assert.Single(outputEvents); WorkflowOutputEvent outputEvent = outputEvents.Single(); - Assert.True((outputEvent.Data) is DerivedOutput); - Assert.Equal("DerivedOutput", ((DerivedOutput)outputEvent.Data!).Name); + DerivedOutput derivedOutput = Assert.IsType(outputEvent.Data); + Assert.Equal("DerivedOutput", derivedOutput.Name); // Verify no error events List errorEvents = events.OfType().ToList(); @@ -200,8 +200,8 @@ public async Task ReturningGrandchildType_WhenBaseTypeIsDeclared_ShouldSucceedAs Assert.Single(outputEvents); WorkflowOutputEvent outputEvent = outputEvents.Single(); - Assert.True((outputEvent.Data) is GrandchildOutput); - Assert.Equal("GrandchildOutput", ((GrandchildOutput)outputEvent.Data!).Name); + GrandchildOutput grandchildOutput = Assert.IsType(outputEvent.Data); + Assert.Equal("GrandchildOutput", grandchildOutput.Name); // Verify no error events List errorEvents = events.OfType().ToList(); @@ -264,7 +264,7 @@ public async Task ReturningExactType_WhenSameTypeIsDeclared_ShouldSucceedAsync() Assert.Single(outputEvents); WorkflowOutputEvent outputEvent = outputEvents.Single(); - Assert.True((outputEvent.Data) is BaseOutput); + Assert.IsType(outputEvent.Data); // Verify no error events List errorEvents = events.OfType().ToList(); diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RouteBuilderTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RouteBuilderTests.cs index e5e5d8f5361..869bf08b7b1 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RouteBuilderTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RouteBuilderTests.cs @@ -357,8 +357,8 @@ public async Task AddPortHandler_RoutesMatchingExternalResponseAsync() CallResult? result = await router.RouteMessageAsync(response, context, cancellationToken: cancellationToken); // Assert - Assert.Single(externalRequestContext.RegisteredPorts); - Assert.Single(externalRequestContext.PostedRequests); + Assert.Equal("port", Assert.Single(externalRequestContext.RegisteredPorts).Id); + Assert.Equal("req-1", Assert.Single(externalRequestContext.PostedRequests).RequestId); Assert.NotNull(result); Assert.True(result!.IsSuccess); Assert.Same(response, result.Result); @@ -384,8 +384,8 @@ public async Task AddPortHandler_UnknownPort_ReturnsExceptionResultAsync() // Assert Assert.NotNull(result); Assert.False(result!.IsSuccess); - Assert.True((result.Exception) is InvalidOperationException); - Assert.Contains("Unknown port", result.Exception!.Message); + InvalidOperationException exception = Assert.IsType(result.Exception); + Assert.Contains("Unknown port", exception.Message); } private static void RegisterVoidHandler(RouteBuilder routeBuilder, HandlerInvocation invocation, HandlerOverload overload) diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/SampleSmokeTest.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/SampleSmokeTest.cs index 644393666c8..aae26d73a1c 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/SampleSmokeTest.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/SampleSmokeTest.cs @@ -553,7 +553,7 @@ internal async Task Test_RunSample_Step14a_SharedState_IsolatedAcrossSubworkflow ? tie.InnerException : error; - Assert.True((actualError) is InvalidOperationException); + Assert.IsType(actualError); } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/StateKeyObjectTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/StateKeyObjectTests.cs index 10595858608..97675dfee09 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/StateKeyObjectTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/StateKeyObjectTests.cs @@ -1,4 +1,6 @@ -using Microsoft.Agents.AI.Workflows.Execution; +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.Agents.AI.Workflows.Execution; namespace Microsoft.Agents.AI.Workflows.UnitTests; From 1ec37fb23420b942ca4db3b2d7604166fd2fbf41 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 28 Aug 2026 14:07:28 -0400 Subject: [PATCH 6/7] chore: formatting --- .../DefaultMcpToolHandlerTests.cs | 14 ++++---- .../DefaultHttpRequestHandlerTests.cs | 34 +++++++++---------- .../ExecutorRouteGeneratorTests.cs | 28 +++++++-------- .../AIAgentHostExecutorTests.cs | 6 ++-- .../AgentEventsTests.cs | 2 +- .../JsonCheckpointSerializationTests.cs | 2 +- .../ChatForwardingExecutorTests.cs | 11 +++--- .../CheckpointParentTests.cs | 4 +-- .../CheckpointVersionToleranceTests.cs | 6 ++-- .../ConcurrentWorkflowBuilderTests.cs | 6 ++-- .../DynamicRequestPortTests.cs | 4 +-- .../EdgeMapSmokeTests.cs | 4 +-- .../ExternalResponsePortCorrelationTests.cs | 20 +++++------ .../FileSystemJsonCheckpointStoreTests.cs | 6 ++-- ...tResponseOutputFilteringAndTaggingTests.cs | 2 +- .../GroupChatOrchestrationTests.cs | 16 ++++----- .../GroupChatWorkflowBuilderTests.cs | 6 ++-- .../HandoffAgentExecutorTests.cs | 2 +- .../HandoffWorkflowBuilderTests.cs | 6 ++-- .../InProcessExecutorEventsTests.cs | 2 +- .../InputWaiterTests.cs | 4 +-- .../JsonSerializationTests.cs | 2 +- .../MagenticManagerTests.cs | 6 ++-- .../MagenticOrchestrationTests.cs | 16 ++++----- .../MagenticProgressLedgerTests.cs | 4 +-- .../MagenticWorkflowBuilderTests.cs | 12 +++---- .../MessageMergerTests.cs | 5 ++- .../OutputTagTests.cs | 2 +- .../PortableValueTests.cs | 4 +-- .../RepresentationTests.cs | 2 +- .../RouteBuilderTests.cs | 20 +++++------ .../SampleSmokeTest.cs | 2 +- .../SequentialWorkflowBuilderTests.cs | 6 ++-- .../StateManagerTests.cs | 18 +++++----- .../TestRequestAgent.cs | 2 +- .../TypeIdVersionToleranceTests.cs | 2 +- .../WorkflowAgentCheckpointIdentityTests.cs | 8 ++--- .../WorkflowBuilderTests.cs | 24 ++++++------- .../WorkflowSessionTests.cs | 2 +- .../WorkflowVisualizerTests.cs | 4 +-- 40 files changed, 162 insertions(+), 164 deletions(-) diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.Mcp.UnitTests/DefaultMcpToolHandlerTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.Mcp.UnitTests/DefaultMcpToolHandlerTests.cs index e99416c5378..5a5b318f055 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.Mcp.UnitTests/DefaultMcpToolHandlerTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.Mcp.UnitTests/DefaultMcpToolHandlerTests.cs @@ -66,10 +66,10 @@ public async Task DisposeAsync_WhenCalled_ShouldCompleteWithoutErrorAsync() DefaultMcpToolHandler handler = new(); // Act - Func act = async () => await handler.DisposeAsync(); + async Task actAsync() => await handler.DisposeAsync(); // Assert - Assert.Null(await Record.ExceptionAsync(act)); + Assert.Null(await Record.ExceptionAsync(actAsync)); } [Fact] @@ -80,10 +80,10 @@ public async Task DisposeAsync_WhenCalledMultipleTimes_ShouldHandleGracefullyAsy // Act await handler.DisposeAsync(); - Func act = async () => await handler.DisposeAsync(); + async Task actAsync() => await handler.DisposeAsync(); // Assert - Second dispose should throw ObjectDisposedException from the semaphore - await Assert.ThrowsAsync(act); + await Assert.ThrowsAsync(actAsync); } #endregion @@ -534,7 +534,7 @@ public async Task InvokeToolAsync_WithListToolsArguments_ShouldThrowArgumentExce try { // Act - Func act = async () => await handler.InvokeToolAsync( + async Task actAsync() => await handler.InvokeToolAsync( serverUrl: "http://localhost:12345/mcp", serverLabel: "test", toolName: DefaultMcpToolHandler.ListToolsToolName, @@ -543,7 +543,7 @@ public async Task InvokeToolAsync_WithListToolsArguments_ShouldThrowArgumentExce connectionName: null); // Assert - ArgumentException exception = await Assert.ThrowsAsync(act); + ArgumentException exception = await Assert.ThrowsAsync(actAsync); Assert.Contains("does not accept tool arguments", exception.Message); } finally @@ -1006,7 +1006,7 @@ public void StripCredentialHeadersOnCrossOrigin_RelativeRequestUri_RetainsAuthor request.Headers.TryAddWithoutValidation("Authorization", "Bearer secret-token"); // Act - Action act = () => OriginPinningHandler.StripCredentialHeadersOnCrossOrigin(request, new Uri("https://trusted.example.com")); + void act() => OriginPinningHandler.StripCredentialHeadersOnCrossOrigin(request, new Uri("https://trusted.example.com")); // Assert — does not throw and leaves the credential in place Assert.Null(Record.Exception(act)); diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/DefaultHttpRequestHandlerTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/DefaultHttpRequestHandlerTests.cs index 2d9aaf7fe15..6970bc336c2 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/DefaultHttpRequestHandlerTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/DefaultHttpRequestHandlerTests.cs @@ -45,7 +45,7 @@ public async Task ConstructorWithNullProviderCreatesInstanceAsync() public void ConstructorWithNullHttpClientThrows() { // Act - Action act = () => _ = new DefaultHttpRequestHandler((HttpClient)null!); + static void act() => _ = new DefaultHttpRequestHandler((HttpClient)null!); // Assert Assert.Throws(act); @@ -86,8 +86,8 @@ public async Task DisposeAsyncDoesNotDisposeCallerSuppliedHttpClientAsync() await handler.DisposeAsync(); // Assert - supplied client remains usable (not disposed) - Func act = async () => await suppliedClient.GetAsync(new Uri(TestUrl)); - Assert.IsNotType(await Record.ExceptionAsync(act)); + async Task actAsync() => await suppliedClient.GetAsync(new Uri(TestUrl)); + Assert.IsNotType(await Record.ExceptionAsync(actAsync)); } #endregion @@ -101,10 +101,10 @@ public async Task SendAsyncWithNullRequestThrowsAsync() await using DefaultHttpRequestHandler handler = new(); // Act - Func act = async () => await handler.SendAsync(null!); + async Task actAsync() => await handler.SendAsync(null!); // Assert - await Assert.ThrowsAsync(act); + await Assert.ThrowsAsync(actAsync); } [Fact] @@ -115,10 +115,10 @@ public async Task SendAsyncWithEmptyUrlThrowsAsync() HttpRequestInfo request = new() { Method = "GET", Url = "" }; // Act - Func act = async () => await handler.SendAsync(request); + async Task actAsync() => await handler.SendAsync(request); // Assert - await Assert.ThrowsAsync(act); + await Assert.ThrowsAsync(actAsync); } [Fact] @@ -129,10 +129,10 @@ public async Task SendAsyncWithEmptyMethodThrowsAsync() HttpRequestInfo request = new() { Method = "", Url = TestUrl }; // Act - Func act = async () => await handler.SendAsync(request); + async Task actAsync() => await handler.SendAsync(request); // Assert - await Assert.ThrowsAsync(act); + await Assert.ThrowsAsync(actAsync); } #endregion @@ -358,10 +358,10 @@ public async Task SendAsyncTimeoutCancelsRequestAsync() }; // Act - Func act = async () => await handler.SendAsync(request); + async Task actAsync() => await handler.SendAsync(request); // Assert - await Assert.ThrowsAnyAsync(act); + await Assert.ThrowsAnyAsync(actAsync); } [Fact] @@ -378,10 +378,10 @@ public async Task SendAsyncFallsBackToOwnedClientWhenProviderReturnsNullAsync() HttpRequestInfo request = new() { Method = "GET", Url = "http://127.0.0.1:1/" }; // Act - owned client will attempt real network and fail, but provider path should have been consulted first. - Func act = async () => await handler.SendAsync(request); + async Task actAsync() => await handler.SendAsync(request); // Assert - await Assert.ThrowsAnyAsync(act); + await Assert.ThrowsAnyAsync(actAsync); Assert.Equal(1, providerCallCount); } @@ -396,10 +396,10 @@ public async Task DisposeAsyncCompletesAsync() DefaultHttpRequestHandler handler = new(); // Act - Func act = async () => await handler.DisposeAsync(); + async Task actAsync() => await handler.DisposeAsync(); // Assert - Assert.Null(await Record.ExceptionAsync(act)); + Assert.Null(await Record.ExceptionAsync(actAsync)); } [Fact] @@ -410,10 +410,10 @@ public async Task DisposeAsyncCalledMultipleTimesSucceedsAsync() // Act await handler.DisposeAsync(); - Func second = async () => await handler.DisposeAsync(); + async Task secondAsync() => await handler.DisposeAsync(); // Assert - Assert.Null(await Record.ExceptionAsync(second)); + Assert.Null(await Record.ExceptionAsync(secondAsync)); } #endregion diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.Generators.UnitTests/ExecutorRouteGeneratorTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.Generators.UnitTests/ExecutorRouteGeneratorTests.cs index d3154c27cfc..d9522872601 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.Generators.UnitTests/ExecutorRouteGeneratorTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.Generators.UnitTests/ExecutorRouteGeneratorTests.cs @@ -335,7 +335,7 @@ private void HandleMessage(string message, IWorkflowContext context) { } var generated = result.RunResult.GeneratedTrees[0]; SyntaxTreeAssert.HaveHierarchy(generated, "OuterClass", "TestExecutor"); - SyntaxTreeAssert.AddHandler(generated, "this.HandleMessage", "string"); + SyntaxTreeAssert.AddHandler(generated, "this.HandleMessage", "string"); } [Fact] @@ -371,7 +371,7 @@ private void HandleMessage(string message, IWorkflowContext context) { } var generated = result.RunResult.GeneratedTrees[0]; SyntaxTreeAssert.HaveHierarchy(generated, "Outer", "Inner", "TestExecutor"); - SyntaxTreeAssert.AddHandler(generated, "this.HandleMessage", "string"); + SyntaxTreeAssert.AddHandler(generated, "this.HandleMessage", "string"); } [Fact] @@ -410,7 +410,7 @@ private void HandleMessage(int message, IWorkflowContext context) { } var generated = result.RunResult.GeneratedTrees[0]; SyntaxTreeAssert.HaveHierarchy(generated, "Level1", "Level2", "Level3", "TestExecutor"); - SyntaxTreeAssert.AddHandler(generated, "this.HandleMessage", "int"); + SyntaxTreeAssert.AddHandler(generated, "this.HandleMessage", "int"); } [Fact] @@ -531,7 +531,7 @@ private void HandleMessage(string message, IWorkflowContext context) { } // - 1 for TestExecutor class // - 1 for ConfigureProtocol method // = 4 pairs minimum - Assert.True((openBraces) >= (4)); + Assert.True(openBraces >= 4); } #endregion @@ -590,7 +590,7 @@ private ValueTask HandleIntAsync(int message, IWorkflowContext context) // Should have both handlers registered SyntaxTreeAssert.AddHandler(generated, "this.HandleString", "string"); - SyntaxTreeAssert.AddHandler(generated, "this.HandleIntAsync", "int"); + SyntaxTreeAssert.AddHandler(generated, "this.HandleIntAsync", "int"); // Verify the generated code compiles with all three partials combined var compilationErrors = result.OutputCompilation.GetDiagnostics() @@ -644,7 +644,7 @@ private void HandleFromFile2(int message, IWorkflowContext context) { } // Both handlers from different files should be registered SyntaxTreeAssert.AddHandler(generated, "this.HandleFromFile1", "string"); - SyntaxTreeAssert.AddHandler(generated, "this.HandleFromFile2", "int"); + SyntaxTreeAssert.AddHandler(generated, "this.HandleFromFile2", "int"); } [Fact] @@ -695,9 +695,9 @@ private void HandleFromFile2(int message, IWorkflowContext context) { } // Verify SendsMessage and YieldsOutput from both partials are combined correctly SyntaxTreeAssert.RegisterSentMessageType(generated, "string"); - SyntaxTreeAssert.RegisterSentMessageType(generated, "int"); - SyntaxTreeAssert.RegisterYieldedOutputType(generated, "string"); - SyntaxTreeAssert.RegisterYieldedOutputType(generated, "int"); + SyntaxTreeAssert.RegisterSentMessageType(generated, "int"); + SyntaxTreeAssert.RegisterYieldedOutputType(generated, "string"); + SyntaxTreeAssert.RegisterYieldedOutputType(generated, "int"); } #endregion @@ -919,8 +919,8 @@ public TestExecutor() : base("test") { } var generated = result.RunResult.GeneratedTrees[0]; SyntaxTreeAssert.RegisterSentMessageType(generated, "global::TestNamespace.MessageA"); - SyntaxTreeAssert.RegisterSentMessageType(generated, "global::TestNamespace.MessageB"); - SyntaxTreeAssert.RegisterSentMessageType(generated, "global::TestNamespace.MessageC"); + SyntaxTreeAssert.RegisterSentMessageType(generated, "global::TestNamespace.MessageB"); + SyntaxTreeAssert.RegisterSentMessageType(generated, "global::TestNamespace.MessageC"); } [Theory] @@ -1053,7 +1053,7 @@ public TestExecutor() : base("test") { } // Verify partial declarations are present SyntaxTreeAssert.HaveHierarchy(generated, "OuterClass", "TestExecutor"); // Verify protocol types are generated - SyntaxTreeAssert.RegisterSentMessageType(generated, "global::TestNamespace.BroadcastMessage"); + SyntaxTreeAssert.RegisterSentMessageType(generated, "global::TestNamespace.BroadcastMessage"); } [Fact] @@ -1083,7 +1083,7 @@ public GenericExecutor() : base("generic") { } var generated = result.RunResult.GeneratedTrees[0]; SyntaxTreeAssert.HaveHierarchy(generated, "GenericExecutor"); - SyntaxTreeAssert.RegisterSentMessageType(generated, "global::TestNamespace.BroadcastMessage"); + SyntaxTreeAssert.RegisterSentMessageType(generated, "global::TestNamespace.BroadcastMessage"); } [Fact] @@ -1193,7 +1193,7 @@ private void HandleMessage(T message, IWorkflowContext context) { } var generated = result.RunResult.GeneratedTrees[0]; SyntaxTreeAssert.HaveHierarchy(generated, "GenericExecutor"); - SyntaxTreeAssert.AddHandler(generated, "this.HandleMessage", "T"); + SyntaxTreeAssert.AddHandler(generated, "this.HandleMessage", "T"); } #endregion diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AIAgentHostExecutorTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AIAgentHostExecutorTests.cs index 2be0f4aec15..2818d230326 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AIAgentHostExecutorTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AIAgentHostExecutorTests.cs @@ -179,18 +179,18 @@ public async Task Test_AgentHostExecutor_ReassignsRolesIFFConfiguredAsync(bool e // Act await executor.Router.RouteMessageAsync(messages, testContext.BindWorkflowContext(executor.Id)); - Func act = async () => await executor.TakeTurnAsync(new(), testContext.BindWorkflowContext(executor.Id)); + async Task actAsync() => await executor.TakeTurnAsync(new(), testContext.BindWorkflowContext(executor.Id)); // Assert bool shouldThrow = includeOtherMessages && !executorSetting; if (shouldThrow) { - await Assert.ThrowsAsync(act); + await Assert.ThrowsAsync(actAsync); } else { - Assert.Null(await Record.ExceptionAsync(act)); + Assert.Null(await Record.ExceptionAsync(actAsync)); } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AgentEventsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AgentEventsTests.cs index 19463c17371..03fc288d430 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AgentEventsTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AgentEventsTests.cs @@ -124,7 +124,7 @@ public async Task StreamingRun_WorkflowStartedEvent_ShouldBeEmittedBefore_SuperS int startedIndex = events.IndexOf(firstStartedEvent!); int superStepIndex = events.IndexOf(firstSuperStepEvent!); - Assert.True((startedIndex) < (superStepIndex)); + Assert.True(startedIndex < superStepIndex); } /// diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/BackwardsCompatibility/JsonCheckpointSerializationTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/BackwardsCompatibility/JsonCheckpointSerializationTests.cs index d689da00bfa..76007f8600b 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/BackwardsCompatibility/JsonCheckpointSerializationTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/BackwardsCompatibility/JsonCheckpointSerializationTests.cs @@ -109,7 +109,7 @@ public void Test_JsonCheckpoint_WorkflowOutputExecutorsWritesMapShape() // The map shape is detectable in the serialized JSON: the property value starts with `{`, not `[`. int idx = json.IndexOf("\"outputExecutorIds\"", System.StringComparison.Ordinal); - Assert.True((idx) > (-1)); + Assert.True(idx > (-1)); int colon = json.IndexOf(':', idx); int firstNonSpace = colon + 1; while (firstNonSpace < json.Length && char.IsWhiteSpace(json[firstNonSpace])) diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/ChatForwardingExecutorTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/ChatForwardingExecutorTests.cs index c2dcdd906bd..9d536400f33 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/ChatForwardingExecutorTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/ChatForwardingExecutorTests.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using Microsoft.Agents.AI.Workflows.Checkpointing; using Microsoft.Extensions.AI; @@ -66,8 +65,8 @@ public async Task Test_ChatForwardingExecutor_DoesNotForwardStringByDefaultAsync ChatForwardingExecutor executor = new(nameof(ChatForwardingExecutor)); // Act - Func> action = () => this.RunForwardMessageTestAsync(executor, TestMessageContent); - await Assert.ThrowsAsync(action); + Task actionAsync() => this.RunForwardMessageTestAsync(executor, TestMessageContent); + await Assert.ThrowsAsync((Func>)actionAsync); } [Theory] @@ -86,19 +85,19 @@ internal async Task Test_ChatForwardingExecutor_ForwardsStringIfConfiguredAsync( ChatForwardingExecutor executor = new(nameof(ChatForwardingExecutor), options); // Act - Func> action = () => this.RunForwardMessageTestAsync(executor, TestMessageContent); + Task actionAsync() => this.RunForwardMessageTestAsync(executor, TestMessageContent); // Assert if (options.StringMessageChatRole is ChatRole chatRole) { - TestWorkflowContext testContext = await action(); + TestWorkflowContext testContext = await actionAsync(); ChatMessage sentMessage = Assert.IsType(Assert.Single(testContext.SentMessages)); Assert.Equivalent(new ChatMessage(chatRole, TestMessageContent), sentMessage); } else { - await Assert.ThrowsAsync(action); + await Assert.ThrowsAsync((Func>)actionAsync); } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/CheckpointParentTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/CheckpointParentTests.cs index fa95c89d9e2..14cfa138d0d 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/CheckpointParentTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/CheckpointParentTests.cs @@ -89,7 +89,7 @@ internal async Task Checkpoint_SubsequentCheckpoints_ShouldChainParentsAsync(Exe } // Assert: We should have at least 3 checkpoints - Assert.True(checkpoints.Count >= (3)); + Assert.True(checkpoints.Count >= 3); // Verify the parent chain Checkpoint stored0 = await ((ICheckpointManager)checkpointManager) @@ -141,7 +141,7 @@ internal async Task Checkpoint_AfterResume_ShouldHaveResumedCheckpointAsParentAs } } - Assert.True(firstRunCheckpoints.Count >= (2)); + Assert.True(firstRunCheckpoints.Count >= 2); CheckpointInfo resumePoint = firstRunCheckpoints[0]; // Dispose the first run to release workflow ownership before resuming. diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/CheckpointVersionToleranceTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/CheckpointVersionToleranceTests.cs index 260d52890a0..c69caeaa924 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/CheckpointVersionToleranceTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/CheckpointVersionToleranceTests.cs @@ -60,7 +60,7 @@ internal async Task Test_Checkpoint_Resumes_AfterAssemblyVersionRewriteAsync(Exe Assert.False(store.MutationApplied); // Resume against the mutated store, which rewrites every Version=X.Y.Z.W in the persisted JSON. - Func resume = async () => + async Task resumeAsync() { await using StreamingRun resumed = await env.WithCheckpointing(checkpointManager) .ResumeStreamingAsync(workflow, checkpoint!); @@ -68,9 +68,9 @@ internal async Task Test_Checkpoint_Resumes_AfterAssemblyVersionRewriteAsync(Exe await foreach (WorkflowEvent _ in resumed.WatchStreamAsync(blockOnPendingRequest: false, cts.Token)) { } - }; + } - Assert.Null(await Record.ExceptionAsync(resume)); + Assert.Null(await Record.ExceptionAsync(resumeAsync)); Assert.True(store.MutationApplied); } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/ConcurrentWorkflowBuilderTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/ConcurrentWorkflowBuilderTests.cs index eae4cc64ff8..27b120bbfd8 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/ConcurrentWorkflowBuilderTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/ConcurrentWorkflowBuilderTests.cs @@ -67,7 +67,7 @@ public void Test_ConcurrentWorkflowBuilder_DefaultDesignationsMatchSpec() Dictionary> designations = workflow.OutputExecutors; Assert.Single(designations, kvp => kvp.Value.Count == 0); - Assert.Equal(6, (designations.Where(kvp => kvp.Value.Contains(OutputTag.Intermediate)))?.Count()); + Assert.Equal(6, designations.Where(kvp => kvp.Value.Contains(OutputTag.Intermediate))?.Count()); } [Fact] @@ -98,8 +98,8 @@ public void Test_ConcurrentWorkflowBuilder_DesignationForNonParticipantThrows() ConcurrentWorkflowBuilder builder = new ConcurrentWorkflowBuilder(participant) .WithIntermediateOutputFrom([stranger]); - Action build = () => builder.Build(); - Assert.Contains("stranger", (Assert.Throws(build)).Message); + void build() => builder.Build(); + Assert.Contains("stranger", Assert.Throws(build).Message); } [Fact] diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/DynamicRequestPortTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/DynamicRequestPortTests.cs index 41904f85193..42f09045683 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/DynamicRequestPortTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/DynamicRequestPortTests.cs @@ -90,8 +90,8 @@ public async Task Test_DynamicRequestPort_ThrowsOnWrongPortAsync() ExternalRequest request = context.Request; ExternalRequest fakeRequest = new(RequestPort.Create("port2").ToPortInfo(), request.RequestId, request.Data); - Func act = async () => await context.InvokeExecutorWithResponseAsync(fakeRequest.CreateResponse(13)); - TargetInvocationException exception = await Assert.ThrowsAsync(act); + async Task actAsync() => await context.InvokeExecutorWithResponseAsync(fakeRequest.CreateResponse(13)); + TargetInvocationException exception = await Assert.ThrowsAsync(actAsync); Assert.IsType(exception.InnerException); } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/EdgeMapSmokeTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/EdgeMapSmokeTests.cs index 18cdd005bca..2b5545a3edf 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/EdgeMapSmokeTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/EdgeMapSmokeTests.cs @@ -82,8 +82,8 @@ async ValueTask RunPortTestAsync(string portId) ExternalResponse responseMessage = new(fakePort.ToPortInfo(), $"RequestFor[{portId}]", new(10)); - Func> mappingTask = async () => await edgeMap.PrepareDeliveryForResponseAsync(responseMessage); - await Assert.ThrowsAsync(mappingTask); + async Task mappingTaskAsync() => await edgeMap.PrepareDeliveryForResponseAsync(responseMessage); + await Assert.ThrowsAsync((Func>)mappingTaskAsync); } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/ExternalResponsePortCorrelationTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/ExternalResponsePortCorrelationTests.cs index 689e3fce1bd..863950527ef 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/ExternalResponsePortCorrelationTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/ExternalResponsePortCorrelationTests.cs @@ -47,8 +47,8 @@ public async Task AddExternalResponseAsync_RejectsForgedPortIdAsync() await runner.RunContext.AddExternalResponseAsync(forged); // Assert: validation fires when the queued delivery is drained. - var act = async () => await runner.RunContext.AdvanceAsync(CancellationToken.None); - var exception = await Assert.ThrowsAsync(act); + async Task actAsync() => await runner.RunContext.AdvanceAsync(CancellationToken.None); + var exception = await Assert.ThrowsAsync((System.Func>)actAsync); string message = exception.Message; Assert.Contains($"'{PortBId}'", message); @@ -71,16 +71,16 @@ public async Task AddExternalResponseAsync_AllowsLegitimateResponseAfterRejected ExternalResponse forged = new(portB.ToPortInfo(), pending.RequestId, new PortableValue(42)); await runner.RunContext.AddExternalResponseAsync(forged); - var rejectAct = async () => await runner.RunContext.AdvanceAsync(CancellationToken.None); - await Assert.ThrowsAsync(rejectAct); + async Task rejectActAsync() => await runner.RunContext.AdvanceAsync(CancellationToken.None); + await Assert.ThrowsAsync((System.Func>)rejectActAsync); // Legitimate responder retries with the correct PortInfo. ExternalResponse legitimate = pending.CreateResponse(42); await runner.RunContext.AddExternalResponseAsync(legitimate); - var legitimateAct = async () => await runner.RunContext.AdvanceAsync(CancellationToken.None); + async Task legitimateActAsync() => await runner.RunContext.AdvanceAsync(CancellationToken.None); - Assert.Null(await Record.ExceptionAsync(legitimateAct)); + Assert.Null(await Record.ExceptionAsync((System.Func>)legitimateActAsync)); Assert.False(((ISuperStepRunner)runner).HasUnservicedRequests); } @@ -98,8 +98,8 @@ public async Task AddExternalResponseAsync_AllowsMatchingPortIdAsync() await runner.RunContext.AddExternalResponseAsync(legitimate); - var act = async () => await runner.RunContext.AdvanceAsync(CancellationToken.None); - Assert.Null(await Record.ExceptionAsync(act)); + async Task actAsync() => await runner.RunContext.AdvanceAsync(CancellationToken.None); + Assert.Null(await Record.ExceptionAsync((System.Func>)actAsync)); Assert.False(((ISuperStepRunner)runner).HasUnservicedRequests); } @@ -115,8 +115,8 @@ public async Task AddExternalResponseAsync_RejectsUnknownRequestIdAsync() await runner.RunContext.AddExternalResponseAsync(stray); - var act = async () => await runner.RunContext.AdvanceAsync(CancellationToken.None); - var exception = await Assert.ThrowsAsync(act); + async Task actAsync() => await runner.RunContext.AdvanceAsync(CancellationToken.None); + var exception = await Assert.ThrowsAsync((System.Func>)actAsync); Assert.Contains("No pending request with ID no-such-request", exception.Message); } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/FileSystemJsonCheckpointStoreTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/FileSystemJsonCheckpointStoreTests.cs index 57d262348f9..0ed9e2ad369 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/FileSystemJsonCheckpointStoreTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/FileSystemJsonCheckpointStoreTests.cs @@ -102,7 +102,7 @@ public async Task CreateCheckpointAsync_ShouldPersistIndexToDiskBeforeDisposeAsy long fileSizeBeforeDispose = indexFile.Length; // Data should already be on disk (file size > 0) before we dispose - Assert.True((fileSizeBeforeDispose) > (0)); + Assert.True(fileSizeBeforeDispose > 0); // Dispose to release file lock before final verification store.Dispose(); @@ -225,8 +225,8 @@ public async Task CreateCheckpointAsync_EscapesInvalidCharsAsync(string invalidC string runId = $"prefix_{invalidChars}_suffix"; - Func createCheckpointAction = async () => await store.CreateCheckpointAsync(runId, TestData); - Assert.Null(await Record.ExceptionAsync(createCheckpointAction)); + async Task createCheckpointActionAsync() => await store.CreateCheckpointAsync(runId, TestData); + Assert.Null(await Record.ExceptionAsync(createCheckpointActionAsync)); } [Fact] diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Futures/Futures.AgentResponseOutputFilteringAndTaggingTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Futures/Futures.AgentResponseOutputFilteringAndTaggingTests.cs index ed40283589a..330584502ba 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Futures/Futures.AgentResponseOutputFilteringAndTaggingTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Futures/Futures.AgentResponseOutputFilteringAndTaggingTests.cs @@ -216,7 +216,7 @@ public async Task Test_Runner_DesignatedIntermediatePocoHasIntermediateTagAsync( List events = await RunAsync(workflow, "go"); WorkflowOutputEvent emitted = Assert.Single(events.OfType()); - Assert.False((emitted) is AgentResponseEvent); + Assert.False(emitted is AgentResponseEvent); Assert.Equivalent(new[] { OutputTag.Intermediate }, emitted.Tags); Assert.True(emitted.IsIntermediate()); } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/GroupChatOrchestrationTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/GroupChatOrchestrationTests.cs index 912738a6668..787b699a342 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/GroupChatOrchestrationTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/GroupChatOrchestrationTests.cs @@ -118,7 +118,7 @@ [new FunctionCallContent(ApprovalCallId, ApprovalToolName)])), await using (StreamingRun firstRun = await env.RunStreamingAsync(workflow, new List { new(ChatRole.User, "hello") })) { - Assert.True((await firstRun.TrySendMessageAsync(new TurnToken(emitEvents: false)))); + Assert.True(await firstRun.TrySendMessageAsync(new TurnToken(emitEvents: false))); using CancellationTokenSource cts = new(TimeSpan.FromSeconds(30)); await foreach (WorkflowEvent evt in firstRun.WatchStreamAsync(blockOnPendingRequest: false, cts.Token)) @@ -142,7 +142,7 @@ [new FunctionCallContent(ApprovalCallId, ApprovalToolName)])), ToolApprovalRequestContent? approvalRequest = pendingRequest!.Data.As(); Assert.NotNull(approvalRequest); - Assert.True((approvalRequest.ToolCall) is FunctionCallContent); + Assert.True(approvalRequest.ToolCall is FunctionCallContent); Assert.Equal(ApprovalToolName, ((FunctionCallContent)approvalRequest.ToolCall).Name); // Deny the request and continue the conversation. @@ -169,7 +169,7 @@ [new FunctionCallContent(ApprovalCallId, ApprovalToolName)])), Assert.Empty(secondRunEvents.OfType() ?? []); Assert.Equal(0, approvalToolCallCount); - Assert.True((agent1CallCount) >= (2)); + Assert.True(agent1CallCount >= 2); Assert.Equal(1, agent2CallCount); Assert.NotNull(finalOutput); @@ -235,7 +235,7 @@ [new FunctionCallContent(FunctionCallId, FunctionName)])), await using (StreamingRun firstRun = await env.RunStreamingAsync(workflow, new List { new(ChatRole.User, "hello") })) { - Assert.True((await firstRun.TrySendMessageAsync(new TurnToken(emitEvents: false)))); + Assert.True(await firstRun.TrySendMessageAsync(new TurnToken(emitEvents: false))); using CancellationTokenSource cts = new(TimeSpan.FromSeconds(30)); await foreach (WorkflowEvent evt in firstRun.WatchStreamAsync(blockOnPendingRequest: false, cts.Token)) @@ -281,7 +281,7 @@ [new FunctionCallContent(FunctionCallId, FunctionName)])), Assert.Empty(resumeEvents.OfType() ?? []); Assert.Empty(resumeEvents.OfType() ?? []); - Assert.True((agent1CallCount) >= (2)); + Assert.True(agent1CallCount >= 2); Assert.Equal(1, agent2CallCount); Assert.NotNull(finalOutput); @@ -312,7 +312,7 @@ private static async Task RunCheckpointedApprovalRoundTripAsync( await using (StreamingRun firstRun = await env.WithCheckpointing(checkpointManager) .RunStreamingAsync(workflow, inputMessages)) { - Assert.True((await firstRun.TrySendMessageAsync(new TurnToken(emitEvents: false)))); + Assert.True(await firstRun.TrySendMessageAsync(new TurnToken(emitEvents: false))); using CancellationTokenSource cts = new(TimeSpan.FromSeconds(30)); await foreach (WorkflowEvent evt in firstRun.WatchStreamAsync(blockOnPendingRequest: false, cts.Token)) @@ -335,7 +335,7 @@ private static async Task RunCheckpointedApprovalRoundTripAsync( ToolApprovalRequestContent? preCheckpoint = firstRunRequest!.Data.As(); Assert.NotNull(preCheckpoint); - Assert.True((preCheckpoint!.ToolCall) is FunctionCallContent); + Assert.True(preCheckpoint!.ToolCall is FunctionCallContent); // Resume on a fresh handle and capture the re-emitted approval request. ExternalRequest? resumedRequest = null; @@ -358,7 +358,7 @@ private static async Task RunCheckpointedApprovalRoundTripAsync( ToolApprovalRequestContent? postResume = resumedRequest!.Data.As(); Assert.NotNull(postResume); Assert.NotNull(postResume!.ToolCall); - Assert.True((postResume.ToolCall) is FunctionCallContent); + Assert.True(postResume.ToolCall is FunctionCallContent); ToolApprovalResponseContent approvalResponse = postResume.CreateResponse(approved: true); await resumed.SendResponseAsync(resumedRequest.CreateResponse(approvalResponse)); diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/GroupChatWorkflowBuilderTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/GroupChatWorkflowBuilderTests.cs index 6067747c408..04d12491d49 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/GroupChatWorkflowBuilderTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/GroupChatWorkflowBuilderTests.cs @@ -177,7 +177,7 @@ public void Test_GroupChatWorkflowBuilder_DefaultDesignationsMatchSpec() Dictionary> designations = workflow.OutputExecutors; Assert.Single(designations, kvp => kvp.Value.Count == 0); - Assert.Equal(3, (designations.Where(kvp => kvp.Value.Contains(OutputTag.Intermediate)))?.Count()); + Assert.Equal(3, designations.Where(kvp => kvp.Value.Contains(OutputTag.Intermediate))?.Count()); } [Fact] @@ -212,8 +212,8 @@ public void Test_GroupChatWorkflowBuilder_DesignationForNonParticipantThrows() .AddParticipants(participant) .WithOutputFrom(stranger); - Action build = () => builder.Build(); - Assert.Contains("stranger", (Assert.Throws(build)).Message); + void build() => builder.Build(); + Assert.Contains("stranger", Assert.Throws(build).Message); } private sealed class RecordingAgent(string name) : AIAgent diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/HandoffAgentExecutorTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/HandoffAgentExecutorTests.cs index a2b88b89492..6b691f9eebb 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/HandoffAgentExecutorTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/HandoffAgentExecutorTests.cs @@ -197,7 +197,7 @@ public async Task Test_HandoffAgentExecutor_PreservesExistingInstructionsAndTool HandoffState state = new(new(false), null); // Act / Assert - Func runStreamingAsync = async () => await executor.HandleAsync(state, testContext); + async Task runStreamingAsync() => await executor.HandleAsync(state, testContext); Assert.Null(await Record.ExceptionAsync(runStreamingAsync)); } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/HandoffWorkflowBuilderTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/HandoffWorkflowBuilderTests.cs index 0c5e38fd691..3a59cda2597 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/HandoffWorkflowBuilderTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/HandoffWorkflowBuilderTests.cs @@ -29,7 +29,7 @@ public void Test_HandoffWorkflowBuilder_DefaultDesignationsMatchSpec() Dictionary> designations = workflow.OutputExecutors; Assert.Single(designations, kvp => kvp.Value.Count == 0); - Assert.Equal(2, (designations.Where(kvp => kvp.Value.Contains(OutputTag.Intermediate)))?.Count()); + Assert.Equal(2, designations.Where(kvp => kvp.Value.Contains(OutputTag.Intermediate))?.Count()); } [Fact] @@ -64,8 +64,8 @@ public void Test_HandoffWorkflowBuilder_DesignationForNonParticipantThrows() .WithHandoff(coordinator, specialist) .WithIntermediateOutputFrom([stranger]); - Action build = () => builder.Build(); - Assert.Contains("stranger", (Assert.Throws(build)).Message); + void build() => builder.Build(); + Assert.Contains("stranger", Assert.Throws(build).Message); } } #pragma warning restore MAAIW001 diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/InProcessExecutorEventsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/InProcessExecutorEventsTests.cs index 04ae02f69b2..7fd89b73c6d 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/InProcessExecutorEventsTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/InProcessExecutorEventsTests.cs @@ -155,7 +155,7 @@ public async Task Test_InProcessExecution_InvokesCheckpointingEventIFFCheckpoint Assert.Empty(run.OutgoingEvents.OfType() ?? []); const int ExpectedSteps = TestFixture.StepsPerInputBatch; - Assert.Equal(ExpectedSteps, (run.OutgoingEvents.OfType())?.Count()); + Assert.Equal(ExpectedSteps, run.OutgoingEvents.OfType()?.Count()); int expectedCheckpoints = useCheckpointing ? ExpectedSteps : 0; Assert.Equal(expectedCheckpoints, run.Checkpoints.Count); diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/InputWaiterTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/InputWaiterTests.cs index c21cf0ab382..d5bb6183e69 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/InputWaiterTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/InputWaiterTests.cs @@ -77,8 +77,8 @@ public async Task InputWaiter_WaitForInputAsync_RespectsCancellationAsync() cts.Cancel(); - Func act = () => waitTask; - await Assert.ThrowsAsync(act); + Task actAsync() => waitTask; + await Assert.ThrowsAsync(actAsync); } [Fact] diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/JsonSerializationTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/JsonSerializationTests.cs index fd6f8121485..580ff8209db 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/JsonSerializationTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/JsonSerializationTests.cs @@ -851,7 +851,7 @@ public void Test_OutOfOrderMetadataProperties_WithoutOption_Fails() // Act & Assert - Without the option, deserialization should fail JsonElement reorderedElement = JsonDocument.Parse(reorderedJson).RootElement; - Action act = () => marshaller.Marshal(reorderedElement); + void act() => marshaller.Marshal(reorderedElement); Assert.Throws(act); } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticManagerTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticManagerTests.cs index 42c285ed692..777247a3b53 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticManagerTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticManagerTests.cs @@ -107,18 +107,18 @@ public async Task Test_MagenticManager_UpdateProgressLedgerAsync(int failures) // Precondition check: ProgressLedger should be not "started" Assert.False(taskContext.ProgressLedger.IsStarted); - Func action = () => manager.UpdateProgressLedgerAsync(taskContext, workflowContext, CancellationToken.None).AsTask(); + Task actionAsync() => manager.UpdateProgressLedgerAsync(taskContext, workflowContext, CancellationToken.None).AsTask(); if (failures >= taskContext.TaskLimits.MaxProgressLedgerRetryCount) { // We expect to see an exception if the number of failures exceeds the maximum retry count - Exception? exception = await Record.ExceptionAsync(action); + Exception? exception = await Record.ExceptionAsync(actionAsync); Assert.NotNull(exception); Assert.False(taskContext.ProgressLedger.IsStarted); } else { - Assert.Null(await Record.ExceptionAsync(action)); + Assert.Null(await Record.ExceptionAsync(actionAsync)); Assert.True(taskContext.ProgressLedger.IsStarted); TestProgressLedgerState.Default.Validate(taskContext.ProgressLedger); } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticOrchestrationTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticOrchestrationTests.cs index a9f1969ba2c..7a5dd2ee463 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticOrchestrationTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticOrchestrationTests.cs @@ -351,7 +351,7 @@ [new ChatMessage(ChatRole.User, "Complex multi-round task")], eventCollector: collectedEvents); // Assert: One plan created, one progress ledger per round, final answer - Assert.Equal(2, (collectedEvents.OfType())?.Count()); + Assert.Equal(2, collectedEvents.OfType()?.Count()); Assert.Single(collectedEvents.OfType()); Assert.Empty(collectedEvents.OfType() ?? []); Assert.NotNull(runResult.Result); @@ -700,7 +700,7 @@ [new ChatMessage(ChatRole.User, "Analyze data")], Assert.NotNull(runResult.Result); Assert.Contains("Task completed with instruction", runResult.Result![0].Text); // Verify the delegation happened (two progress ledger events for two rounds) - Assert.Equal(2, (collectedEvents.OfType())?.Count()); + Assert.Equal(2, collectedEvents.OfType()?.Count()); } [Fact] @@ -1100,7 +1100,7 @@ [new ChatMessage(ChatRole.User, "Test stall decrement")], eventCollector: collectedEvents); // Assert: Three progress ledger updates, no stall-triggered reset - Assert.Equal(3, (collectedEvents.OfType())?.Count()); + Assert.Equal(3, collectedEvents.OfType()?.Count()); // One initial plan, no replans (agent returns go directly to coordination, no replan) Assert.Single(collectedEvents.OfType()); @@ -1167,7 +1167,7 @@ [new ChatMessage(ChatRole.User, "Test consecutive stalls")], eventCollector: collectedEvents); // Assert: Two pre-reset coordination rounds + one post-reset round = 3 ledger events - Assert.Equal(3, (collectedEvents.OfType())?.Count()); + Assert.Equal(3, collectedEvents.OfType()?.Count()); // One initial plan + one stall-triggered reset replan (no normal re-entry replans anymore) Assert.Single(collectedEvents.OfType()); @@ -1272,7 +1272,7 @@ [new ChatMessage(ChatRole.User, "Execute task")], // Assert: Multiple replan events emitted, final answer produced Assert.NotEmpty(allEvents.OfType()); - Assert.True((allEvents.OfType()).Count() >= (2)); + Assert.True(allEvents.OfType().Count() >= 2); Assert.NotNull(fourthResult.Result); Assert.Contains("Completed after multiple revisions", fourthResult.Result![0].Text); } @@ -1290,8 +1290,8 @@ public void Empty_Team_Build_Throws() .RequirePlanSignoff(false); // Act & Assert: Build() should throw because the team is empty. - Action buildAction = () => builder.Build(); - Assert.Contains("participant", (Assert.Throws(buildAction)).Message); + void buildAction() => builder.Build(); + Assert.Contains("participant", Assert.Throws(buildAction).Message); } [Fact] @@ -1364,7 +1364,7 @@ public async Task Terminated_Context_Rejects_New_MessagesAsync() Exception actual = errorEvent!.Exception is System.Reflection.TargetInvocationException tie && tie.InnerException != null ? tie.InnerException : errorEvent.Exception!; - Assert.True((actual) is InvalidOperationException); + Assert.True(actual is InvalidOperationException); Assert.Contains("terminated", actual.Message); } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticProgressLedgerTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticProgressLedgerTests.cs index b176838586f..bd2dc2dd79e 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticProgressLedgerTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticProgressLedgerTests.cs @@ -70,7 +70,7 @@ Some text after the JSON embed. """); // Act - Action action = () => _ = message.ExtractJson(); + void action() => _ = message.ExtractJson(); // Assert Assert.ThrowsAny(action); @@ -86,7 +86,7 @@ public void Test_ExtractJson_FailsWhenNoJson() """); // Act - Action action = () => _ = message.ExtractJson(); + void action() => _ = message.ExtractJson(); // Assert Assert.ThrowsAny(action); diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticWorkflowBuilderTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticWorkflowBuilderTests.cs index 429bb3df891..b143caf5f21 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticWorkflowBuilderTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MagenticWorkflowBuilderTests.cs @@ -30,7 +30,7 @@ public void Test_MagenticWorkflowBuilder_DefaultDesignationsMatchSpec() Dictionary> designations = workflow.OutputExecutors; Assert.Single(designations, kvp => kvp.Value.Count == 0); - Assert.Equal(2, (designations.Where(kvp => kvp.Value.Contains(OutputTag.Intermediate)))?.Count()); + Assert.Equal(2, designations.Where(kvp => kvp.Value.Contains(OutputTag.Intermediate))?.Count()); } [Fact] @@ -66,8 +66,8 @@ public void Test_MagenticWorkflowBuilder_DesignationForNonParticipantThrows() .RequirePlanSignoff(false) .WithIntermediateOutputFrom([stranger]); - Action build = () => builder.Build(); - Assert.Contains("Stranger", (Assert.Throws(build)).Message); + void build() => builder.Build(); + Assert.Contains("Stranger", Assert.Throws(build).Message); } [Fact] @@ -111,10 +111,10 @@ public void Test_MagenticWorkflowBuilder_ProgressLedgerOverrideWithoutSchema_Thr .WithPromptOverrides(new MagenticPromptOverrides { ProgressLedgerPrompt = "Answer for {task} with no schema placeholder" }); // Act - Action build = () => builder.Build(); + void build() => builder.Build(); // Assert - Assert.Contains("{schema}", (Assert.Throws(build)).Message); + Assert.Contains("{schema}", Assert.Throws(build).Message); } [Fact] @@ -130,7 +130,7 @@ public void Test_MagenticWorkflowBuilder_ProgressLedgerOverrideWithSchema_Builds .WithPromptOverrides(new MagenticPromptOverrides { ProgressLedgerPrompt = "Answer for {task}\n{schema}" }); // Act - Action build = () => builder.Build(); + void build() => builder.Build(); // Assert Assert.Null(Record.Exception(build)); diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MessageMergerTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MessageMergerTests.cs index c0c7abddef0..9ac460033a6 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MessageMergerTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MessageMergerTests.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. using System; -using System.Linq; using Microsoft.Extensions.AI; namespace Microsoft.Agents.AI.Workflows.UnitTests; @@ -35,8 +34,8 @@ public void Test_MessageMerger_AssemblesMessage() Assert.Equal(TestAuthorName1, message.AuthorName); Assert.Equal(TestAgentId1, response.AgentId); Assert.NotNull(response.CreatedAt); - Assert.True((response.CreatedAt.Value) >= (creationTime)); - Assert.True((response.CreatedAt.Value) >= ((creationTime) - (TimeSpan.FromSeconds(5))) && (response.CreatedAt.Value) <= ((creationTime) + (TimeSpan.FromSeconds(5)))); + Assert.True(response.CreatedAt.Value >= creationTime); + Assert.True(response.CreatedAt.Value >= (creationTime - TimeSpan.FromSeconds(5)) && response.CreatedAt.Value <= (creationTime + TimeSpan.FromSeconds(5))); Assert.Equal(creationTime, message.CreatedAt); Assert.Single(message.Contents); Assert.Null(response.FinishReason); diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/OutputTagTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/OutputTagTests.cs index 3a23343ccf2..f5a1d542898 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/OutputTagTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/OutputTagTests.cs @@ -18,7 +18,7 @@ public void Test_OutputTag_KnownValues() public void Test_OutputTag_EqualityIsOrdinalOnValue() { Assert.Equal(OutputTag.Intermediate, OutputTag.Intermediate); - Assert.True((OutputTag.Intermediate == OutputTag.Intermediate)); + Assert.True(OutputTag.Intermediate == OutputTag.Intermediate); // Same Value via independent construction (via JSON round-trip below) is equal. OutputTag rebuilt = JsonSerializer.Deserialize("\"intermediate\"", WorkflowsJsonUtilities.DefaultOptions); diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/PortableValueTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/PortableValueTests.cs index 62536f920f5..b3e58b22b09 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/PortableValueTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/PortableValueTests.cs @@ -58,7 +58,7 @@ public async Task Test_DelayedSerializationRoundtripAsync(T value) Assert.False(portableValue.Is(out _)); Assert.True(portableValue.Is(out object? obj)); - Assert.False((obj) is T); + Assert.False(obj is T); PortableValue nestedPortableValue = Assert.IsType(obj); Assert.Equal(value, nestedPortableValue.As()); @@ -76,7 +76,7 @@ public async Task Test_DelayedSerializationRoundtripObjectAsync() Assert.False(portableValue.Is(out _)); Assert.True(portableValue.Is(out object? obj)); - Assert.False((obj) is ChatMessage); + Assert.False(obj is ChatMessage); PortableValue nestedPortableValue = Assert.IsType(obj); Assert.Equal(value, nestedPortableValue.As()); diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RepresentationTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RepresentationTests.cs index 38b8cd51c14..21002268306 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RepresentationTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RepresentationTests.cs @@ -66,7 +66,7 @@ public async Task Test_ExecutorBinding_InfosAsync() int expectedTests = workflowAssembly.GetTypes() .Count(type => type != bindingBaseType && bindingBaseType.IsAssignableFrom(type)); - Assert.True((expectedTests) > 0); + Assert.True(expectedTests > 0); if (expectedTests > testsRun + 1) { diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RouteBuilderTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RouteBuilderTests.cs index 869bf08b7b1..d97d1f97a9b 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RouteBuilderTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RouteBuilderTests.cs @@ -241,10 +241,10 @@ public void AddHandler_ForPortableValue_ThrowsInvalidOperationException() RouteBuilder routeBuilder = new(null); // Act - Action act = () => routeBuilder.AddHandler((message, context) => { }); + void act() => routeBuilder.AddHandler((message, context) => { }); // Assert - Assert.Contains("Use AddCatchAll()", (Assert.Throws(act)).Message); + Assert.Contains("Use AddCatchAll()", Assert.Throws(act).Message); } [Fact] @@ -255,10 +255,10 @@ public void AddHandler_DuplicateRegistrationWithoutOverwrite_ThrowsArgumentExcep routeBuilder.AddHandler((message, context) => { }); // Act - Action act = () => routeBuilder.AddHandler((message, context) => { }); + void act() => routeBuilder.AddHandler((message, context) => { }); // Assert - Assert.Contains("already registered", (Assert.Throws(act)).Message); + Assert.Contains("already registered", Assert.Throws(act).Message); } [Fact] @@ -268,10 +268,10 @@ public void AddHandler_OverwriteWithoutExistingRegistration_ThrowsArgumentExcept RouteBuilder routeBuilder = new(null); // Act - Action act = () => routeBuilder.AddHandler((message, context) => { }, overwrite: true); + void act() => routeBuilder.AddHandler((message, context) => { }, overwrite: true); // Assert - Assert.Contains("has not yet been registered", (Assert.Throws(act)).Message); + Assert.Contains("has not yet been registered", Assert.Throws(act).Message); } [Fact] @@ -299,10 +299,10 @@ public void AddCatchAll_DuplicateRegistrationWithoutOverwrite_ThrowsInvalidOpera routeBuilder.AddCatchAll((message, context) => { }); // Act - Action act = () => routeBuilder.AddCatchAll((message, context) => { }); + void act() => routeBuilder.AddCatchAll((message, context) => { }); // Assert - Assert.Contains("already registered", (Assert.Throws(act)).Message); + Assert.Contains("already registered", Assert.Throws(act).Message); } [Fact] @@ -329,10 +329,10 @@ public void AddPortHandler_WithoutExternalRequestContext_ThrowsInvalidOperationE RouteBuilder routeBuilder = new(null); // Act - Action act = () => routeBuilder.AddPortHandler("port", (response, context, cancellationToken) => default, out _); + void act() => routeBuilder.AddPortHandler("port", (response, context, cancellationToken) => default, out _); // Assert - Assert.Contains("external request context is required", (Assert.Throws(act)).Message); + Assert.Contains("external request context is required", Assert.Throws(act).Message); } [Fact] diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/SampleSmokeTest.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/SampleSmokeTest.cs index aae26d73a1c..fa1ce7b974f 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/SampleSmokeTest.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/SampleSmokeTest.cs @@ -356,7 +356,7 @@ public void ProcessInput(string newInput) int agentBookmark = this._bookmarks[i]; int count = this._history.Count - agentBookmark; - Assert.True((count) >= (0)); + Assert.True(count >= 0); foreach (string input in this._history.Skip(agentBookmark).ToList()) { diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/SequentialWorkflowBuilderTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/SequentialWorkflowBuilderTests.cs index 0885ea81de4..9735b792898 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/SequentialWorkflowBuilderTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/SequentialWorkflowBuilderTests.cs @@ -117,7 +117,7 @@ public void Test_SequentialWorkflowBuilder_DefaultDesignationsMatchSpec() Dictionary> designations = workflow.OutputExecutors; Assert.Single(designations, kvp => kvp.Value.Count == 0); - Assert.Equal(3, (designations.Where(kvp => kvp.Value.Contains(OutputTag.Intermediate)))?.Count()); + Assert.Equal(3, designations.Where(kvp => kvp.Value.Contains(OutputTag.Intermediate))?.Count()); } [Fact] @@ -148,8 +148,8 @@ public void Test_SequentialWorkflowBuilder_DesignationForNonParticipantThrows() SequentialWorkflowBuilder builder = new SequentialWorkflowBuilder(participant) .WithIntermediateOutputFrom([stranger]); - Action build = () => builder.Build(); - Assert.Contains("stranger", (Assert.Throws(build)).Message); + void build() => builder.Build(); + Assert.Contains("stranger", Assert.Throws(build).Message); } [Fact] diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/StateManagerTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/StateManagerTests.cs index 3f64d6431f6..2ddafbeb5bc 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/StateManagerTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/StateManagerTests.cs @@ -359,15 +359,15 @@ private static async Task RunConflictingUpdatesTest_WriteVsWriteAsync(string? sc await manager.WriteStateAsync(scopeSelfView, Key1, Value1); await manager.WriteStateAsync(scopeOtherView, Key1, Value2); - Func act = async () => await manager.PublishUpdatesAsync(tracer: null); + async Task actAsync() => await manager.PublishUpdatesAsync(tracer: null); if (isSharedScope) { - await Assert.ThrowsAsync(act); + await Assert.ThrowsAsync(actAsync); } else { - Assert.Null(await Record.ExceptionAsync(act)); + Assert.Null(await Record.ExceptionAsync(actAsync)); } } @@ -391,15 +391,15 @@ private static async Task RunConflictingUpdatesTest_WriteVsDeleteAsync(string? s // Act: Update the key from one executor and delete it from another await manager.WriteStateAsync(scopeSelfView, Key1, "newValue"); await manager.ClearStateAsync(scopeOtherView, Key1); - Func act = async () => await manager.PublishUpdatesAsync(tracer: null); + async Task actAsync() => await manager.PublishUpdatesAsync(tracer: null); if (isSharedScope) { - await Assert.ThrowsAsync(act); + await Assert.ThrowsAsync(actAsync); } else { - Assert.Null(await Record.ExceptionAsync(act)); + Assert.Null(await Record.ExceptionAsync(actAsync)); } } @@ -423,16 +423,16 @@ private static async Task RunConflictingUpdatesTest_WriteVsClearAsync(string? sc // Act: Update the key from one, and clear the entire scope from another await manager.WriteStateAsync(scopeSelfView, Key1, "newValue"); await manager.ClearStateAsync(scopeOtherView); - Func act = async () => await manager.PublishUpdatesAsync(tracer: null); + async Task actAsync() => await manager.PublishUpdatesAsync(tracer: null); // Assert if (isSharedScope) { - await Assert.ThrowsAsync(act); + await Assert.ThrowsAsync(actAsync); } else { - Assert.Null(await Record.ExceptionAsync(act)); + Assert.Null(await Record.ExceptionAsync(actAsync)); } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestRequestAgent.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestRequestAgent.cs index a3c25e1ff19..dc7977adc97 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestRequestAgent.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestRequestAgent.cs @@ -200,7 +200,7 @@ public void ProcessResponse(ToolApprovalResponseContent response, TestRequestAge if (session.UnservicedRequests.TryGetValue(response.RequestId, out ToolApprovalRequestContent? request)) { Assert.True(response.Approved); - Assert.Equal((FunctionCallContent)request.ToolCall, ((FunctionCallContent)response.ToolCall)); + Assert.Equal((FunctionCallContent)request.ToolCall, (FunctionCallContent)response.ToolCall); session.ServicedRequests.Add(response.RequestId); session.UnservicedRequests.Remove(response.RequestId); } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TypeIdVersionToleranceTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TypeIdVersionToleranceTests.cs index e7201fc1e3a..7077020ba44 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TypeIdVersionToleranceTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TypeIdVersionToleranceTests.cs @@ -106,7 +106,7 @@ public void Test_Equals_IgnoresAssemblyVersion() TypeId v2 = new($"{ProbeSimpleAssemblyName}, Version=2.0.0.0", ProbeTypeFullName); Assert.True(v1.Equals(v2)); - Assert.True((v1 == v2)); + Assert.True(v1 == v2); Assert.Equal(v2.GetHashCode(), v1.GetHashCode()); } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowAgentCheckpointIdentityTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowAgentCheckpointIdentityTests.cs index 39263fef6ea..c243c70e16b 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowAgentCheckpointIdentityTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowAgentCheckpointIdentityTests.cs @@ -74,10 +74,10 @@ public async Task WorkflowAgentSession_WithoutStableInnerAgentIds_FailsAcrossRec // checkpoint against the reconstructed workflow. AgentSession resumedSession = await secondGeneration.DeserializeSessionAsync(serialized); - Func resumeAndRun = () => secondGeneration.RunAsync("Anything else?", resumedSession); + Task resumeAndRunAsync() => secondGeneration.RunAsync("Anything else?", resumedSession); // Assert: the second run throws because the reconstructed executor ids no longer match the checkpoint. - InvalidDataException exception = await Assert.ThrowsAsync(resumeAndRun); + InvalidDataException exception = await Assert.ThrowsAsync(resumeAndRunAsync); Assert.Equal("The specified checkpoint is not compatible with the workflow associated with this runner.", exception.Message); } @@ -100,10 +100,10 @@ public async Task WorkflowAgentSession_WithStableIdsButChangedInnerNames_FailsAc // Act: deserialization succeeds; the incompatibility surfaces on the resuming run. AgentSession resumedSession = await secondGeneration.DeserializeSessionAsync(serialized); - Func resumeAndRun = () => secondGeneration.RunAsync("Anything else?", resumedSession); + Task resumeAndRunAsync() => secondGeneration.RunAsync("Anything else?", resumedSession); // Assert: changing a set name invalidates the executor identity even though the id is stable. - InvalidDataException exception = await Assert.ThrowsAsync(resumeAndRun); + InvalidDataException exception = await Assert.ThrowsAsync(resumeAndRunAsync); Assert.Equal("The specified checkpoint is not compatible with the workflow associated with this runner.", exception.Message); } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowBuilderTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowBuilderTests.cs index 9ef21c3424c..2ddf996c06d 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowBuilderTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowBuilderTests.cs @@ -27,27 +27,27 @@ protected override ProtocolBuilder ConfigureProtocol(ProtocolBuilder protocolBui [Fact] public void Test_Validation_FailsWhenUnboundExecutors() { - Func act = () => + static Workflow act() { return new WorkflowBuilder("start") .AddEdge(new NoOpExecutor("start"), "unbound") .Build(); - }; + } - Assert.Throws(act); + Assert.Throws((Func)act); } [Fact] public void Test_Validation_FailsWhenUnreachableExecutors() { - Func act = () => + static Workflow act() { return new WorkflowBuilder("start") .BindExecutor(new NoOpExecutor("start")) .AddEdge(new NoOpExecutor("unreachable"), new NoOpExecutor("also-unreachable")) .Build(); - }; - Assert.Throws(act); + } + Assert.Throws((Func)act); } [Fact] @@ -104,14 +104,14 @@ public void Test_RebindToDifferent_Disallowed() NoOpExecutor executor1 = new("start"); SomeOtherNoOpExecutor executor2 = new("start"); - Func act = () => + Workflow act() { return new WorkflowBuilder("start") .AddEdge(executor1, executor2) .Build(); - }; + } - Assert.Throws(act); + Assert.Throws((Func)act); } [Fact] @@ -291,11 +291,11 @@ public void AddChain_WhenExecutorRepeats_Throws() NoOpExecutor middle = new("middle"); // Act - Action act = () => new WorkflowBuilder(source.Id) + void act() => new WorkflowBuilder(source.Id) .AddChain(source, [middle, source]); // Assert - Assert.Equal("executors", (Assert.Throws(act)).ParamName); + Assert.Equal("executors", Assert.Throws(act).ParamName); } [Fact] @@ -636,7 +636,7 @@ public void Build_WhenValidationFails_DoesNotMarkCoreWorkflow() WorkflowBuilder builder = new("unbound"); // Act - Action build = () => builder.Build(); + void build() => builder.Build(); // Assert Assert.Throws(build); diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowSessionTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowSessionTests.cs index 6059347375e..a114a2b6e36 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowSessionTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowSessionTests.cs @@ -93,7 +93,7 @@ public void TryGetRequestEnvelope_ReturnsEnvelopeWhenPortDeclaresEnvelopeType() ExternalRequest request = ExternalRequest.Create(port, new TestEnvelope()); Assert.True(WorkflowSession.TryGetRequestEnvelope(request, ports, out IExternalRequestEnvelope? envelope)); - Assert.True((envelope) is TestEnvelope); + Assert.True(envelope is TestEnvelope); } [Fact] diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowVisualizerTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowVisualizerTests.cs index a6a486c60d1..7bd1d567e94 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowVisualizerTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowVisualizerTests.cs @@ -128,8 +128,8 @@ public void Test_WorkflowViz_FanIn_EdgeGroup() var fanInLine = fanInLines[0]; var firstQuote = fanInLine.IndexOf('"'); var secondQuote = fanInLine.IndexOf('"', firstQuote + 1); - Assert.True((firstQuote) > (-1)); - Assert.True((secondQuote) > (-1)); + Assert.True(firstQuote > (-1)); + Assert.True(secondQuote > (-1)); var fanInNodeId = fanInLine.Substring(firstQuote + 1, secondQuote - firstQuote - 1); Assert.False(string.IsNullOrEmpty(fanInNodeId)); From 91d66a7112c1c27417b0ef0a92d876b1b5f8bbd0 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 28 Aug 2026 14:16:41 -0400 Subject: [PATCH 7/7] tests: ensure collection order Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../MessageMergerTests.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MessageMergerTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MessageMergerTests.cs index 9ac460033a6..41f227ff089 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MessageMergerTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MessageMergerTests.cs @@ -88,7 +88,12 @@ public void Test_MessageMerger_PreservesFirstSeenMessageOrder() AgentResponse response = merger.ComputeMerged(responseId); // Assert - Assert.Equal("first", response.Messages[0].Text); + Assert.Collection( + response.Messages, + message => Assert.Equal("first", message.Text), + message => Assert.Equal("second", message.Text), + message => Assert.Equal("third", message.Text), + message => Assert.Equal("fourth", message.Text)); Assert.Equal(now.AddMinutes(1), response.Messages[0].CreatedAt); Assert.Equal(now.AddMinutes(-1), response.Messages[2].CreatedAt); }