// Copyright (c) Microsoft. All rights reserved. using System; using Microsoft.Agents.AI.Workflows; using Microsoft.Extensions.AI; using Microsoft.Extensions.DependencyInjection; using Moq; namespace Microsoft.Agents.AI.DevUI.UnitTests; /// /// Unit tests for DevUI service collection extensions. /// Tests verify that workflows and agents can be resolved even when registered non-conventionally. /// public class DevUIExtensionsTests { /// /// Verifies that AddDevUI throws ArgumentNullException when services collection is null. /// [Fact] public void AddDevUI_NullServices_ThrowsArgumentNullException() { IServiceCollection services = null!; Assert.Throws(() => services.AddDevUI()); } /// /// Verifies that GetRequiredKeyedService throws for non-existent keys. /// [Fact] public void AddDevUI_GetRequiredKeyedServiceNonExistent_ThrowsInvalidOperationException() { // Arrange var services = new ServiceCollection(); services.AddDevUI(); var serviceProvider = services.BuildServiceProvider(); // Act & Assert Assert.Throws(() => serviceProvider.GetRequiredKeyedService("non-existent")); } /// /// Verifies that an agent with null name can be resolved by its workflow. /// [Fact] public void AddDevUI_WorkflowWithName_CanBeResolved_AsAIAgent() { // Arrange var services = new ServiceCollection(); var mockChatClient = new Mock(); var agent1 = new ChatClientAgent(mockChatClient.Object, "Test 1", name: null); var agent2 = new ChatClientAgent(mockChatClient.Object, "Test 2", name: null); var workflow = AgentWorkflowBuilder.BuildSequential(agent1, agent2); services.AddKeyedSingleton("workflow", workflow); services.AddDevUI(); var serviceProvider = services.BuildServiceProvider(); // Act var resolvedWorkflowAsAgent = serviceProvider.GetKeyedService("workflow"); // Assert Assert.NotNull(resolvedWorkflowAsAgent); Assert.Null(resolvedWorkflowAsAgent.Name); } /// /// Verifies that an agent with null name can be resolved by its workflow. /// [Fact] public void AddDevUI_MultipleWorkflowsWithName_CanBeResolved_AsAIAgent() { var services = new ServiceCollection(); var mockChatClient = new Mock(); var agent1 = new ChatClientAgent(mockChatClient.Object, "Test 1", name: null); var agent2 = new ChatClientAgent(mockChatClient.Object, "Test 2", name: null); var workflow1 = AgentWorkflowBuilder.BuildSequential(agent1, agent2); var workflow2 = AgentWorkflowBuilder.BuildSequential(agent1, agent2); services.AddKeyedSingleton("workflow1", workflow1); services.AddKeyedSingleton("workflow2", workflow2); services.AddDevUI(); var serviceProvider = services.BuildServiceProvider(); var resolvedWorkflow1AsAgent = serviceProvider.GetKeyedService("workflow1"); Assert.NotNull(resolvedWorkflow1AsAgent); Assert.Null(resolvedWorkflow1AsAgent.Name); var resolvedWorkflow2AsAgent = serviceProvider.GetKeyedService("workflow2"); Assert.NotNull(resolvedWorkflow2AsAgent); Assert.Null(resolvedWorkflow2AsAgent.Name); Assert.False(resolvedWorkflow1AsAgent == resolvedWorkflow2AsAgent); } /// /// Verifies that an agent with null name can be resolved by its workflow. /// [Fact] public void AddDevUI_NonKeyedWorkflow_CanBeResolved_AsAIAgent() { var services = new ServiceCollection(); var mockChatClient = new Mock(); var agent1 = new ChatClientAgent(mockChatClient.Object, "Test 1", name: null); var agent2 = new ChatClientAgent(mockChatClient.Object, "Test 2", name: null); var workflow = AgentWorkflowBuilder.BuildSequential(agent1, agent2); services.AddKeyedSingleton("workflow", workflow); services.AddDevUI(); var serviceProvider = services.BuildServiceProvider(); var resolvedWorkflowAsAgent = serviceProvider.GetKeyedService("workflow"); Assert.NotNull(resolvedWorkflowAsAgent); Assert.Null(resolvedWorkflowAsAgent.Name); } /// /// Verifies that an agent with null name can be resolved by its workflow. /// [Fact] public void AddDevUI_NonKeyedWorkflow_PlusKeyedWorkflow_CanBeResolved_AsAIAgent() { var services = new ServiceCollection(); var mockChatClient = new Mock(); var agent1 = new ChatClientAgent(mockChatClient.Object, "Test 1", name: null); var agent2 = new ChatClientAgent(mockChatClient.Object, "Test 2", name: null); var workflow = AgentWorkflowBuilder.BuildSequential("standardname", agent1, agent2); var keyedWorkflow = AgentWorkflowBuilder.BuildSequential("keyedname", agent1, agent2); services.AddSingleton(workflow); services.AddKeyedSingleton("keyed", keyedWorkflow); services.AddDevUI(); var serviceProvider = services.BuildServiceProvider(); // resolve a workflow with the same name as workflow's name (which is registered without a key) var standardAgent = serviceProvider.GetKeyedService("standardname"); Assert.NotNull(standardAgent); Assert.Equal("standardname", standardAgent.Name); var keyedAgent = serviceProvider.GetKeyedService("keyed"); Assert.NotNull(keyedAgent); Assert.Equal("keyedname", keyedAgent.Name); var nonExisting = serviceProvider.GetKeyedService("random-non-existing!!!"); Assert.Null(nonExisting); } /// /// Verifies that an agent registered with a different key than its name can be resolved by key. /// [Fact] public void AddDevUI_AgentRegisteredWithDifferentKey_CanBeResolvedByKey() { // Arrange var services = new ServiceCollection(); const string AgentName = "actual-agent-name"; const string RegistrationKey = "different-key"; var mockChatClient = new Mock(); var agent = new ChatClientAgent(mockChatClient.Object, "Test", AgentName); services.AddKeyedSingleton(RegistrationKey, agent); services.AddDevUI(); var serviceProvider = services.BuildServiceProvider(); // Act var resolvedAgent = serviceProvider.GetKeyedService(RegistrationKey); // Assert Assert.NotNull(resolvedAgent); // The resolved agent should have the agent's name, not the registration key Assert.Equal(AgentName, resolvedAgent.Name); } /// /// Verifies that an agent registered with a different key than its name can be resolved by key. /// [Fact] public void AddDevUI_Keyed_AndStandard_BothCanBeResolved() { // Arrange var services = new ServiceCollection(); var mockChatClient = new Mock(); var defaultAgent = new ChatClientAgent(mockChatClient.Object, "default", "default"); var keyedAgent = new ChatClientAgent(mockChatClient.Object, "keyed", "keyed"); services.AddSingleton(defaultAgent); services.AddKeyedSingleton("keyed-registration", keyedAgent); services.AddDevUI(); var serviceProvider = services.BuildServiceProvider(); var resolvedKeyedAgent = serviceProvider.GetKeyedService("keyed-registration"); Assert.NotNull(resolvedKeyedAgent); Assert.Equal("keyed", resolvedKeyedAgent.Name); // resolving default agent based on its name, not on the registration-key var resolvedDefaultAgent = serviceProvider.GetKeyedService("default"); Assert.NotNull(resolvedDefaultAgent); Assert.Equal("default", resolvedDefaultAgent.Name); } /// /// Verifies that the DevUI fallback handler error message includes helpful information. /// [Fact] public void AddDevUI_InvalidResolution_ErrorMessageIsInformative() { // Arrange var services = new ServiceCollection(); services.AddDevUI(); var serviceProvider = services.BuildServiceProvider(); const string InvalidKey = "invalid-key-name"; // Act & Assert var exception = Assert.Throws(() => serviceProvider.GetRequiredKeyedService(InvalidKey)); } }