adds unit tests for devui integration

This commit is contained in:
tommasodotnet
2026-02-09 19:40:49 +01:00
Unverified
parent 3c952911ce
commit 2c23d03717
6 changed files with 1154 additions and 0 deletions
+1
View File
@@ -449,6 +449,7 @@
<Project Path="tests/OpenAIResponse.IntegrationTests/OpenAIResponse.IntegrationTests.csproj" />
</Folder>
<Folder Name="/Tests/UnitTests/">
<Project Path="tests/Aspire.Hosting.AgentFramework.DevUI.UnitTests/Aspire.Hosting.AgentFramework.DevUI.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.A2A.UnitTests/Microsoft.Agents.AI.A2A.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Abstractions.UnitTests/Microsoft.Agents.AI.Abstractions.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.AGUI.UnitTests/Microsoft.Agents.AI.AGUI.UnitTests.csproj" />
@@ -0,0 +1,186 @@
// Copyright (c) Microsoft. All rights reserved.
using Aspire.Hosting.AgentFramework;
namespace Aspire.Hosting.AgentFramework.DevUI.UnitTests;
/// <summary>
/// Unit tests for the <see cref="AgentEntityInfo"/> record.
/// </summary>
public class AgentEntityInfoTests
{
#region Constructor Tests
/// <summary>
/// Verifies that the Id property is set from the constructor parameter.
/// </summary>
[Fact]
public void Constructor_WithId_SetsIdProperty()
{
// Arrange & Act
var info = new AgentEntityInfo("test-agent");
// Assert
Assert.Equal("test-agent", info.Id);
}
/// <summary>
/// Verifies that the Description property is set when provided.
/// </summary>
[Fact]
public void Constructor_WithDescription_SetsDescriptionProperty()
{
// Arrange & Act
var info = new AgentEntityInfo("test-agent", "A test agent");
// Assert
Assert.Equal("A test agent", info.Description);
}
/// <summary>
/// Verifies that the Description property is null when not provided.
/// </summary>
[Fact]
public void Constructor_WithoutDescription_DescriptionIsNull()
{
// Arrange & Act
var info = new AgentEntityInfo("test-agent");
// Assert
Assert.Null(info.Description);
}
#endregion
#region Default Value Tests
/// <summary>
/// Verifies that Name defaults to the Id value when not explicitly set.
/// </summary>
[Fact]
public void Name_NotSet_DefaultsToId()
{
// Arrange & Act
var info = new AgentEntityInfo("test-agent");
// Assert
Assert.Equal("test-agent", info.Name);
}
/// <summary>
/// Verifies that Name can be overridden with a custom value.
/// </summary>
[Fact]
public void Name_Set_ReturnsCustomValue()
{
// Arrange & Act
var info = new AgentEntityInfo("test-agent") { Name = "Custom Name" };
// Assert
Assert.Equal("Custom Name", info.Name);
}
/// <summary>
/// Verifies that Type defaults to "agent".
/// </summary>
[Fact]
public void Type_NotSet_DefaultsToAgent()
{
// Arrange & Act
var info = new AgentEntityInfo("test-agent");
// Assert
Assert.Equal("agent", info.Type);
}
/// <summary>
/// Verifies that Type can be overridden with a custom value.
/// </summary>
[Fact]
public void Type_Set_ReturnsCustomValue()
{
// Arrange & Act
var info = new AgentEntityInfo("test-agent") { Type = "workflow" };
// Assert
Assert.Equal("workflow", info.Type);
}
/// <summary>
/// Verifies that Framework defaults to "agent_framework".
/// </summary>
[Fact]
public void Framework_NotSet_DefaultsToAgentFramework()
{
// Arrange & Act
var info = new AgentEntityInfo("test-agent");
// Assert
Assert.Equal("agent_framework", info.Framework);
}
/// <summary>
/// Verifies that Framework can be overridden with a custom value.
/// </summary>
[Fact]
public void Framework_Set_ReturnsCustomValue()
{
// Arrange & Act
var info = new AgentEntityInfo("test-agent") { Framework = "custom_framework" };
// Assert
Assert.Equal("custom_framework", info.Framework);
}
#endregion
#region Record Equality Tests
/// <summary>
/// Verifies that two AgentEntityInfo records with identical values are equal.
/// </summary>
[Fact]
public void Equality_SameValues_AreEqual()
{
// Arrange
var info1 = new AgentEntityInfo("agent", "description");
var info2 = new AgentEntityInfo("agent", "description");
// Assert
Assert.Equal(info1, info2);
}
/// <summary>
/// Verifies that two AgentEntityInfo records with different Ids are not equal.
/// </summary>
[Fact]
public void Equality_DifferentIds_AreNotEqual()
{
// Arrange
var info1 = new AgentEntityInfo("agent1");
var info2 = new AgentEntityInfo("agent2");
// Assert
Assert.NotEqual(info1, info2);
}
/// <summary>
/// Verifies that with-expression creates a modified copy.
/// </summary>
[Fact]
public void WithExpression_ModifiesProperty_CreatesNewInstance()
{
// Arrange
var original = new AgentEntityInfo("agent", "Original description");
// Act
var modified = original with { Description = "Modified description" };
// Assert
Assert.Equal("Original description", original.Description);
Assert.Equal("Modified description", modified.Description);
Assert.Equal(original.Id, modified.Id);
}
#endregion
}
@@ -0,0 +1,568 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Linq;
using Aspire.Hosting.AgentFramework;
using Aspire.Hosting.ApplicationModel;
using Moq;
namespace Aspire.Hosting.AgentFramework.DevUI.UnitTests;
/// <summary>
/// Unit tests for the <see cref="AgentFrameworkBuilderExtensions"/> class.
/// </summary>
public class AgentFrameworkBuilderExtensionsTests
{
#region AddDevUI Validation Tests
/// <summary>
/// Verifies that AddDevUI throws ArgumentNullException when builder is null.
/// </summary>
[Fact]
public void AddDevUI_NullBuilder_ThrowsArgumentNullException()
{
// Act & Assert
var exception = Assert.Throws<ArgumentNullException>(
() => AgentFrameworkBuilderExtensions.AddDevUI(null!, "devui"));
Assert.Equal("builder", exception.ParamName);
}
/// <summary>
/// Verifies that AddDevUI throws ArgumentNullException when name is null.
/// </summary>
[Fact]
public void AddDevUI_NullName_ThrowsArgumentNullException()
{
// Arrange
var builder = DistributedApplication.CreateBuilder();
// Act & Assert
var exception = Assert.Throws<ArgumentNullException>(
() => builder.AddDevUI(null!));
Assert.Equal("name", exception.ParamName);
}
/// <summary>
/// Verifies that AddDevUI creates a resource with the specified name.
/// </summary>
[Fact]
public void AddDevUI_ValidName_CreatesResourceWithName()
{
// Arrange
var builder = DistributedApplication.CreateBuilder();
// Act
var resourceBuilder = builder.AddDevUI("my-devui");
// Assert
Assert.Equal("my-devui", resourceBuilder.Resource.Name);
}
/// <summary>
/// Verifies that AddDevUI creates a DevUIResource.
/// </summary>
[Fact]
public void AddDevUI_ReturnsDevUIResourceBuilder()
{
// Arrange
var builder = DistributedApplication.CreateBuilder();
// Act
var resourceBuilder = builder.AddDevUI("devui");
// Assert
Assert.IsType<DevUIResource>(resourceBuilder.Resource);
}
/// <summary>
/// Verifies that AddDevUI with port configures the endpoint.
/// </summary>
[Fact]
public void AddDevUI_WithPort_ConfiguresEndpointWithPort()
{
// Arrange
var builder = DistributedApplication.CreateBuilder();
// Act
var resourceBuilder = builder.AddDevUI("devui", port: 8090);
// Assert
var endpoint = resourceBuilder.Resource.Annotations
.OfType<EndpointAnnotation>()
.FirstOrDefault(e => e.Name == "http");
Assert.NotNull(endpoint);
Assert.Equal(8090, endpoint.Port);
}
/// <summary>
/// Verifies that AddDevUI without port leaves port as null for dynamic allocation.
/// </summary>
[Fact]
public void AddDevUI_WithoutPort_EndpointHasDynamicPort()
{
// Arrange
var builder = DistributedApplication.CreateBuilder();
// Act
var resourceBuilder = builder.AddDevUI("devui");
// Assert
var endpoint = resourceBuilder.Resource.Annotations
.OfType<EndpointAnnotation>()
.FirstOrDefault(e => e.Name == "http");
Assert.NotNull(endpoint);
Assert.Null(endpoint.Port);
}
#endregion
#region WithAgentService Validation Tests
/// <summary>
/// Verifies that WithAgentService throws ArgumentNullException when builder is null.
/// </summary>
[Fact]
public void WithAgentService_NullBuilder_ThrowsArgumentNullException()
{
// Arrange
var appBuilder = DistributedApplication.CreateBuilder();
var mockAgentService = CreateMockAgentServiceBuilder(appBuilder, "agent-service");
// Act & Assert
var exception = Assert.Throws<ArgumentNullException>(
() => AgentFrameworkBuilderExtensions.WithAgentService<IResourceWithEndpoints>(null!, mockAgentService));
Assert.Equal("builder", exception.ParamName);
}
/// <summary>
/// Verifies that WithAgentService throws ArgumentNullException when agentService is null.
/// </summary>
[Fact]
public void WithAgentService_NullAgentService_ThrowsArgumentNullException()
{
// Arrange
var appBuilder = DistributedApplication.CreateBuilder();
var devuiBuilder = appBuilder.AddDevUI("devui");
// Act & Assert
var exception = Assert.Throws<ArgumentNullException>(
() => devuiBuilder.WithAgentService<IResourceWithEndpoints>(null!));
Assert.Equal("agentService", exception.ParamName);
}
#endregion
#region WithAgentService Annotation Tests
/// <summary>
/// Verifies that WithAgentService adds an AgentServiceAnnotation to the resource.
/// </summary>
[Fact]
public void WithAgentService_ValidService_AddsAnnotation()
{
// Arrange
var appBuilder = DistributedApplication.CreateBuilder();
var devuiBuilder = appBuilder.AddDevUI("devui");
var agentService = CreateMockAgentServiceBuilder(appBuilder, "writer-agent");
// Act
devuiBuilder.WithAgentService(agentService);
// Assert
var annotation = devuiBuilder.Resource.Annotations
.OfType<AgentServiceAnnotation>()
.FirstOrDefault();
Assert.NotNull(annotation);
Assert.Same(agentService.Resource, annotation.AgentService);
}
/// <summary>
/// Verifies that WithAgentService defaults to agent name being the resource name.
/// </summary>
[Fact]
public void WithAgentService_NoAgents_DefaultsToResourceNameAsAgent()
{
// Arrange
var appBuilder = DistributedApplication.CreateBuilder();
var devuiBuilder = appBuilder.AddDevUI("devui");
var agentService = CreateMockAgentServiceBuilder(appBuilder, "writer-agent");
// Act
devuiBuilder.WithAgentService(agentService);
// Assert
var annotation = devuiBuilder.Resource.Annotations
.OfType<AgentServiceAnnotation>()
.First();
Assert.Single(annotation.Agents);
Assert.Equal("writer-agent", annotation.Agents[0].Id);
}
/// <summary>
/// Verifies that WithAgentService with explicit agents uses those agents.
/// </summary>
[Fact]
public void WithAgentService_WithAgents_UsesProvidedAgents()
{
// Arrange
var appBuilder = DistributedApplication.CreateBuilder();
var devuiBuilder = appBuilder.AddDevUI("devui");
var agentService = CreateMockAgentServiceBuilder(appBuilder, "multi-agent-service");
var agents = new[]
{
new AgentEntityInfo("agent1", "First agent"),
new AgentEntityInfo("agent2", "Second agent")
};
// Act
devuiBuilder.WithAgentService(agentService, agents: agents);
// Assert
var annotation = devuiBuilder.Resource.Annotations
.OfType<AgentServiceAnnotation>()
.First();
Assert.Equal(2, annotation.Agents.Count);
Assert.Equal("agent1", annotation.Agents[0].Id);
Assert.Equal("agent2", annotation.Agents[1].Id);
}
/// <summary>
/// Verifies that WithAgentService with custom prefix uses that prefix.
/// </summary>
[Fact]
public void WithAgentService_WithEntityIdPrefix_UsesProvidedPrefix()
{
// Arrange
var appBuilder = DistributedApplication.CreateBuilder();
var devuiBuilder = appBuilder.AddDevUI("devui");
var agentService = CreateMockAgentServiceBuilder(appBuilder, "writer-agent");
// Act
devuiBuilder.WithAgentService(agentService, entityIdPrefix: "custom-prefix");
// Assert
var annotation = devuiBuilder.Resource.Annotations
.OfType<AgentServiceAnnotation>()
.First();
Assert.Equal("custom-prefix", annotation.EntityIdPrefix);
}
/// <summary>
/// Verifies that WithAgentService without prefix leaves EntityIdPrefix null.
/// </summary>
[Fact]
public void WithAgentService_NoEntityIdPrefix_PrefixIsNull()
{
// Arrange
var appBuilder = DistributedApplication.CreateBuilder();
var devuiBuilder = appBuilder.AddDevUI("devui");
var agentService = CreateMockAgentServiceBuilder(appBuilder, "writer-agent");
// Act
devuiBuilder.WithAgentService(agentService);
// Assert
var annotation = devuiBuilder.Resource.Annotations
.OfType<AgentServiceAnnotation>()
.First();
Assert.Null(annotation.EntityIdPrefix);
}
#endregion
#region Chaining Tests
/// <summary>
/// Verifies that WithAgentService returns the builder for chaining.
/// </summary>
[Fact]
public void WithAgentService_ReturnsSameBuilder_ForChaining()
{
// Arrange
var appBuilder = DistributedApplication.CreateBuilder();
var devuiBuilder = appBuilder.AddDevUI("devui");
var agentService = CreateMockAgentServiceBuilder(appBuilder, "writer-agent");
// Act
var result = devuiBuilder.WithAgentService(agentService);
// Assert
Assert.Same(devuiBuilder, result);
}
/// <summary>
/// Verifies that multiple WithAgentService calls can be chained.
/// </summary>
[Fact]
public void WithAgentService_MultipleCalls_AddsMultipleAnnotations()
{
// Arrange
var appBuilder = DistributedApplication.CreateBuilder();
var devuiBuilder = appBuilder.AddDevUI("devui");
var writerService = CreateMockAgentServiceBuilder(appBuilder, "writer-agent");
var editorService = CreateMockAgentServiceBuilder(appBuilder, "editor-agent");
// Act
devuiBuilder
.WithAgentService(writerService)
.WithAgentService(editorService);
// Assert
var annotations = devuiBuilder.Resource.Annotations
.OfType<AgentServiceAnnotation>()
.ToList();
Assert.Equal(2, annotations.Count);
Assert.Contains(annotations, a => a.AgentService.Name == "writer-agent");
Assert.Contains(annotations, a => a.AgentService.Name == "editor-agent");
}
/// <summary>
/// Verifies that AddDevUI returns a builder that can be chained with WithAgentService.
/// </summary>
[Fact]
public void AddDevUI_CanChainWithAgentService()
{
// Arrange
var appBuilder = DistributedApplication.CreateBuilder();
var agentService = CreateMockAgentServiceBuilder(appBuilder, "writer-agent");
// Act - Chain AddDevUI with WithAgentService
var result = appBuilder.AddDevUI("devui").WithAgentService(agentService);
// Assert
Assert.NotNull(result);
var annotation = result.Resource.Annotations
.OfType<AgentServiceAnnotation>()
.FirstOrDefault();
Assert.NotNull(annotation);
}
#endregion
#region Relationship Tests
/// <summary>
/// Verifies that WithAgentService creates a relationship annotation.
/// </summary>
[Fact]
public void WithAgentService_CreatesRelationshipAnnotation()
{
// Arrange
var appBuilder = DistributedApplication.CreateBuilder();
var devuiBuilder = appBuilder.AddDevUI("devui");
var agentService = CreateMockAgentServiceBuilder(appBuilder, "writer-agent");
// Act
devuiBuilder.WithAgentService(agentService);
// Assert
var relationship = devuiBuilder.Resource.Annotations
.OfType<ResourceRelationshipAnnotation>()
.FirstOrDefault();
Assert.NotNull(relationship);
Assert.Equal("agent-backend", relationship.Type);
}
/// <summary>
/// Verifies that multiple WithAgentService calls create multiple relationship annotations.
/// </summary>
[Fact]
public void WithAgentService_MultipleCalls_CreatesMultipleRelationships()
{
// Arrange
var appBuilder = DistributedApplication.CreateBuilder();
var devuiBuilder = appBuilder.AddDevUI("devui");
var writerService = CreateMockAgentServiceBuilder(appBuilder, "writer-agent");
var editorService = CreateMockAgentServiceBuilder(appBuilder, "editor-agent");
// Act
devuiBuilder
.WithAgentService(writerService)
.WithAgentService(editorService);
// Assert
var relationships = devuiBuilder.Resource.Annotations
.OfType<ResourceRelationshipAnnotation>()
.ToList();
Assert.Equal(2, relationships.Count);
Assert.All(relationships, r => Assert.Equal("agent-backend", r.Type));
}
#endregion
#region Agent Metadata Tests
/// <summary>
/// Verifies that agent description is preserved when specified.
/// </summary>
[Fact]
public void WithAgentService_AgentWithDescription_PreservesDescription()
{
// Arrange
var appBuilder = DistributedApplication.CreateBuilder();
var devuiBuilder = appBuilder.AddDevUI("devui");
var agentService = CreateMockAgentServiceBuilder(appBuilder, "writer-agent");
var agents = new[] { new AgentEntityInfo("writer", "Writes creative stories") };
// Act
devuiBuilder.WithAgentService(agentService, agents: agents);
// Assert
var annotation = devuiBuilder.Resource.Annotations
.OfType<AgentServiceAnnotation>()
.First();
Assert.Equal("Writes creative stories", annotation.Agents[0].Description);
}
/// <summary>
/// Verifies that custom agent properties are preserved.
/// </summary>
[Fact]
public void WithAgentService_CustomAgentProperties_ArePreserved()
{
// Arrange
var appBuilder = DistributedApplication.CreateBuilder();
var devuiBuilder = appBuilder.AddDevUI("devui");
var agentService = CreateMockAgentServiceBuilder(appBuilder, "custom-service");
var agents = new[]
{
new AgentEntityInfo("custom-agent")
{
Name = "Custom Display Name",
Type = "workflow",
Framework = "custom_framework"
}
};
// Act
devuiBuilder.WithAgentService(agentService, agents: agents);
// Assert
var annotation = devuiBuilder.Resource.Annotations
.OfType<AgentServiceAnnotation>()
.First();
var agent = annotation.Agents[0];
Assert.Equal("custom-agent", agent.Id);
Assert.Equal("Custom Display Name", agent.Name);
Assert.Equal("workflow", agent.Type);
Assert.Equal("custom_framework", agent.Framework);
}
/// <summary>
/// Verifies that empty agents array can be explicitly provided and is respected.
/// </summary>
[Fact]
public void WithAgentService_EmptyAgentsArray_UsesEmptyArray()
{
// Arrange
var appBuilder = DistributedApplication.CreateBuilder();
var devuiBuilder = appBuilder.AddDevUI("devui");
var agentService = CreateMockAgentServiceBuilder(appBuilder, "writer-agent");
var emptyAgents = Array.Empty<AgentEntityInfo>();
// Act
devuiBuilder.WithAgentService(agentService, agents: emptyAgents);
// Assert
var annotation = devuiBuilder.Resource.Annotations
.OfType<AgentServiceAnnotation>()
.First();
// When explicitly passing an empty array, the extension method respects it
// This is the expected behavior - explicit empty means "discover at runtime"
Assert.Empty(annotation.Agents);
}
#endregion
#region Edge Case Tests
/// <summary>
/// Verifies that AddDevUI can be called multiple times with different names.
/// </summary>
[Fact]
public void AddDevUI_MultipleCalls_CreatesSeparateResources()
{
// Arrange
var appBuilder = DistributedApplication.CreateBuilder();
// Act
var devui1 = appBuilder.AddDevUI("devui1");
var devui2 = appBuilder.AddDevUI("devui2");
// Assert
Assert.NotSame(devui1.Resource, devui2.Resource);
Assert.Equal("devui1", devui1.Resource.Name);
Assert.Equal("devui2", devui2.Resource.Name);
}
/// <summary>
/// Verifies that same agent service can be added to multiple DevUI resources.
/// </summary>
[Fact]
public void WithAgentService_SameServiceToMultipleDevUI_Works()
{
// Arrange
var appBuilder = DistributedApplication.CreateBuilder();
var devui1 = appBuilder.AddDevUI("devui1");
var devui2 = appBuilder.AddDevUI("devui2");
var agentService = CreateMockAgentServiceBuilder(appBuilder, "shared-agent");
// Act
devui1.WithAgentService(agentService);
devui2.WithAgentService(agentService);
// Assert
var annotation1 = devui1.Resource.Annotations.OfType<AgentServiceAnnotation>().Single();
var annotation2 = devui2.Resource.Annotations.OfType<AgentServiceAnnotation>().Single();
Assert.Same(annotation1.AgentService, annotation2.AgentService);
}
/// <summary>
/// Verifies that WithAgentService works with different entity ID prefixes for the same service.
/// </summary>
[Fact]
public void WithAgentService_DifferentPrefixesToDifferentDevUI_Works()
{
// Arrange
var appBuilder = DistributedApplication.CreateBuilder();
var devui1 = appBuilder.AddDevUI("devui1");
var devui2 = appBuilder.AddDevUI("devui2");
var agentService = CreateMockAgentServiceBuilder(appBuilder, "writer-agent");
// Act
devui1.WithAgentService(agentService, entityIdPrefix: "prefix1");
devui2.WithAgentService(agentService, entityIdPrefix: "prefix2");
// Assert
var annotation1 = devui1.Resource.Annotations.OfType<AgentServiceAnnotation>().Single();
var annotation2 = devui2.Resource.Annotations.OfType<AgentServiceAnnotation>().Single();
Assert.Equal("prefix1", annotation1.EntityIdPrefix);
Assert.Equal("prefix2", annotation2.EntityIdPrefix);
}
#endregion
#region Helper Methods
/// <summary>
/// Creates a mock agent service builder for testing.
/// Uses a minimal resource implementation that satisfies IResourceWithEndpoints.
/// </summary>
private static IResourceBuilder<IResourceWithEndpoints> CreateMockAgentServiceBuilder(
IDistributedApplicationBuilder appBuilder,
string name)
{
// Create a mock resource that implements IResourceWithEndpoints
var mockResource = new Mock<IResourceWithEndpoints>();
mockResource.Setup(r => r.Name).Returns(name);
mockResource.Setup(r => r.Annotations).Returns(new ResourceAnnotationCollection());
var mockBuilder = new Mock<IResourceBuilder<IResourceWithEndpoints>>();
mockBuilder.Setup(b => b.Resource).Returns(mockResource.Object);
mockBuilder.Setup(b => b.ApplicationBuilder).Returns(appBuilder);
return mockBuilder.Object;
}
#endregion
}
@@ -0,0 +1,168 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using Aspire.Hosting.AgentFramework;
using Aspire.Hosting.ApplicationModel;
using Moq;
namespace Aspire.Hosting.AgentFramework.DevUI.UnitTests;
/// <summary>
/// Unit tests for the <see cref="AgentServiceAnnotation"/> class.
/// </summary>
public class AgentServiceAnnotationTests
{
#region Constructor Validation Tests
/// <summary>
/// Verifies that passing null for agentService throws ArgumentNullException.
/// </summary>
[Fact]
public void Constructor_NullAgentService_ThrowsArgumentNullException()
{
// Act & Assert
Assert.Throws<ArgumentNullException>(() => new AgentServiceAnnotation(null!));
}
/// <summary>
/// Verifies that a valid agentService can be used to create the annotation.
/// </summary>
[Fact]
public void Constructor_ValidAgentService_CreatesAnnotation()
{
// Arrange
var mockResource = new Mock<IResource>();
mockResource.Setup(r => r.Name).Returns("test-service");
// Act
var annotation = new AgentServiceAnnotation(mockResource.Object);
// Assert
Assert.NotNull(annotation);
Assert.Same(mockResource.Object, annotation.AgentService);
}
#endregion
#region Property Tests
/// <summary>
/// Verifies that AgentService property returns the value passed to constructor.
/// </summary>
[Fact]
public void AgentService_ReturnsConstructorValue()
{
// Arrange
var mockResource = new Mock<IResource>();
mockResource.Setup(r => r.Name).Returns("my-service");
// Act
var annotation = new AgentServiceAnnotation(mockResource.Object);
// Assert
Assert.Same(mockResource.Object, annotation.AgentService);
}
/// <summary>
/// Verifies that EntityIdPrefix returns null when not specified.
/// </summary>
[Fact]
public void EntityIdPrefix_NotSpecified_ReturnsNull()
{
// Arrange
var mockResource = new Mock<IResource>();
mockResource.Setup(r => r.Name).Returns("test-service");
// Act
var annotation = new AgentServiceAnnotation(mockResource.Object);
// Assert
Assert.Null(annotation.EntityIdPrefix);
}
/// <summary>
/// Verifies that EntityIdPrefix returns the value passed to constructor.
/// </summary>
[Fact]
public void EntityIdPrefix_Specified_ReturnsValue()
{
// Arrange
var mockResource = new Mock<IResource>();
mockResource.Setup(r => r.Name).Returns("test-service");
// Act
var annotation = new AgentServiceAnnotation(mockResource.Object, entityIdPrefix: "custom-prefix");
// Assert
Assert.Equal("custom-prefix", annotation.EntityIdPrefix);
}
/// <summary>
/// Verifies that Agents returns empty collection when not specified.
/// </summary>
[Fact]
public void Agents_NotSpecified_ReturnsEmptyCollection()
{
// Arrange
var mockResource = new Mock<IResource>();
mockResource.Setup(r => r.Name).Returns("test-service");
// Act
var annotation = new AgentServiceAnnotation(mockResource.Object);
// Assert
Assert.NotNull(annotation.Agents);
Assert.Empty(annotation.Agents);
}
/// <summary>
/// Verifies that Agents returns the list passed to constructor.
/// </summary>
[Fact]
public void Agents_Specified_ReturnsValue()
{
// Arrange
var mockResource = new Mock<IResource>();
mockResource.Setup(r => r.Name).Returns("test-service");
var agents = new[] { new AgentEntityInfo("agent1"), new AgentEntityInfo("agent2") };
// Act
var annotation = new AgentServiceAnnotation(mockResource.Object, agents: agents);
// Assert
Assert.Equal(2, annotation.Agents.Count);
Assert.Equal("agent1", annotation.Agents[0].Id);
Assert.Equal("agent2", annotation.Agents[1].Id);
}
#endregion
#region Full Constructor Tests
/// <summary>
/// Verifies that all constructor parameters are correctly stored.
/// </summary>
[Fact]
public void Constructor_AllParameters_SetsAllProperties()
{
// Arrange
var mockResource = new Mock<IResource>();
mockResource.Setup(r => r.Name).Returns("full-service");
var agents = new[] { new AgentEntityInfo("writer", "Writes stories") };
// Act
var annotation = new AgentServiceAnnotation(
mockResource.Object,
entityIdPrefix: "writer-backend",
agents: agents);
// Assert
Assert.Same(mockResource.Object, annotation.AgentService);
Assert.Equal("writer-backend", annotation.EntityIdPrefix);
Assert.Single(annotation.Agents);
Assert.Equal("writer", annotation.Agents[0].Id);
Assert.Equal("Writes stories", annotation.Agents[0].Description);
}
#endregion
}
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<TargetFrameworks></TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\aspire-integration\Aspire.Hosting.AgentFramework.DevUI\Aspire.Hosting.AgentFramework.DevUI.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,215 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Linq;
using System.Net.Sockets;
using Aspire.Hosting.ApplicationModel;
namespace Aspire.Hosting.AgentFramework.DevUI.UnitTests;
/// <summary>
/// Unit tests for the <see cref="DevUIResource"/> class.
/// </summary>
public class DevUIResourceTests
{
#region Constructor Tests
/// <summary>
/// Verifies that the resource name is correctly set.
/// </summary>
[Fact]
public void Constructor_WithName_SetsName()
{
// Arrange & Act
var resource = new DevUIResource("test-devui");
// Assert
Assert.Equal("test-devui", resource.Name);
}
/// <summary>
/// Verifies that the resource implements IResourceWithEndpoints.
/// </summary>
[Fact]
public void Resource_ImplementsIResourceWithEndpoints()
{
// Arrange & Act
var resource = new DevUIResource("test-devui");
// Assert
Assert.IsAssignableFrom<IResourceWithEndpoints>(resource);
}
/// <summary>
/// Verifies that the resource implements IResourceWithWaitSupport.
/// </summary>
[Fact]
public void Resource_ImplementsIResourceWithWaitSupport()
{
// Arrange & Act
var resource = new DevUIResource("test-devui");
// Assert
Assert.IsAssignableFrom<IResourceWithWaitSupport>(resource);
}
#endregion
#region Endpoint Annotation Tests
/// <summary>
/// Verifies that the resource has an HTTP endpoint annotation when port is specified.
/// </summary>
[Fact]
public void Constructor_WithPort_AddsEndpointAnnotation()
{
// Arrange & Act
var resource = CreateResourceWithPort(8090);
// Assert
var endpoint = resource.Annotations.OfType<EndpointAnnotation>().FirstOrDefault();
Assert.NotNull(endpoint);
Assert.Equal("http", endpoint.Name);
Assert.Equal(8090, endpoint.Port);
}
/// <summary>
/// Verifies that the endpoint annotation has correct protocol type.
/// </summary>
[Fact]
public void EndpointAnnotation_HasTcpProtocol()
{
// Arrange
var resource = CreateResourceWithPort(8080);
// Act
var endpoint = resource.Annotations.OfType<EndpointAnnotation>().First();
// Assert
Assert.Equal(ProtocolType.Tcp, endpoint.Protocol);
}
/// <summary>
/// Verifies that the endpoint annotation has HTTP URI scheme.
/// </summary>
[Fact]
public void EndpointAnnotation_HasHttpUriScheme()
{
// Arrange
var resource = CreateResourceWithPort(8080);
// Act
var endpoint = resource.Annotations.OfType<EndpointAnnotation>().First();
// Assert
Assert.Equal("http", endpoint.UriScheme);
}
/// <summary>
/// Verifies that the endpoint is not proxied.
/// </summary>
[Fact]
public void EndpointAnnotation_IsNotProxied()
{
// Arrange
var resource = CreateResourceWithPort(8080);
// Act
var endpoint = resource.Annotations.OfType<EndpointAnnotation>().First();
// Assert
Assert.False(endpoint.IsProxied);
}
/// <summary>
/// Verifies that the endpoint target host is localhost.
/// </summary>
[Fact]
public void EndpointAnnotation_TargetHostIsLocalhost()
{
// Arrange
var resource = CreateResourceWithPort(8080);
// Act
var endpoint = resource.Annotations.OfType<EndpointAnnotation>().First();
// Assert
Assert.Equal("localhost", endpoint.TargetHost);
}
/// <summary>
/// Verifies that the endpoint has no fixed port when null is passed.
/// </summary>
[Fact]
public void Constructor_WithNullPort_EndpointHasNullPort()
{
// Arrange & Act
var resource = CreateResourceWithPort(null);
// Assert
var endpoint = resource.Annotations.OfType<EndpointAnnotation>().FirstOrDefault();
Assert.NotNull(endpoint);
Assert.Null(endpoint.Port);
}
#endregion
#region PrimaryEndpoint Tests
/// <summary>
/// Verifies that PrimaryEndpoint returns an endpoint reference.
/// </summary>
[Fact]
public void PrimaryEndpoint_ReturnsEndpointReference()
{
// Arrange
var resource = CreateResourceWithPort(8080);
// Act
var endpoint = resource.PrimaryEndpoint;
// Assert
Assert.NotNull(endpoint);
Assert.Same(resource, endpoint.Resource);
}
/// <summary>
/// Verifies that PrimaryEndpoint returns the same instance on multiple calls.
/// </summary>
[Fact]
public void PrimaryEndpoint_MultipleCalls_ReturnsSameInstance()
{
// Arrange
var resource = CreateResourceWithPort(8080);
// Act
var endpoint1 = resource.PrimaryEndpoint;
var endpoint2 = resource.PrimaryEndpoint;
// Assert
Assert.Same(endpoint1, endpoint2);
}
#endregion
/// <summary>
/// Creates a DevUIResource using reflection to access the internal constructor.
/// This is necessary because the (name, port) constructor is internal.
/// </summary>
private static DevUIResource CreateResourceWithPort(int? port)
{
// Use reflection to call the internal constructor
var constructorInfo = typeof(DevUIResource).GetConstructor(
System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic,
null,
[typeof(string), typeof(int?)],
null);
if (constructorInfo is null)
{
throw new InvalidOperationException("Could not find internal DevUIResource constructor");
}
return (DevUIResource)constructorInfo.Invoke(["test-devui", port]);
}
}