mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
e5f7b9c260
* support reflection for discovery of resources and scripts in class-based skills * fix format issues * refactor samples to use reflection * Validate resource member signatures during discovery Add discovery-time validation in AgentClassSkill.DiscoverResources() to fail fast when [AgentSkillResource] is applied to members with incompatible signatures: - Reject indexer properties (getter has parameters) - Reject methods with parameters other than IServiceProvider or CancellationToken Throws InvalidOperationException with actionable error messages instead of allowing silent runtime failures when ReadAsync invokes the AIFunction with no named arguments. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * prevent duplicates --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
226 lines
6.6 KiB
C#
226 lines
6.6 KiB
C#
// Copyright (c) Microsoft. All rights reserved.
|
|
|
|
using System;
|
|
using System.Reflection;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace Microsoft.Agents.AI.UnitTests.AgentSkills;
|
|
|
|
/// <summary>
|
|
/// Unit tests for <see cref="AgentInlineSkillResource"/>.
|
|
/// </summary>
|
|
public sealed class AgentInlineSkillResourceTests
|
|
{
|
|
[Fact]
|
|
public async Task ReadAsync_StaticValue_ReturnsValueAsync()
|
|
{
|
|
// Arrange
|
|
var resource = new AgentInlineSkillResource("config", "my-value");
|
|
|
|
// Act
|
|
var result = await resource.ReadAsync();
|
|
|
|
// Assert
|
|
Assert.Equal("my-value", result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ReadAsync_StaticObjectValue_ReturnsSameInstanceAsync()
|
|
{
|
|
// Arrange
|
|
var obj = new object();
|
|
var resource = new AgentInlineSkillResource("ref", obj);
|
|
|
|
// Act
|
|
var result = await resource.ReadAsync();
|
|
|
|
// Assert
|
|
Assert.Same(obj, result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ReadAsync_Delegate_InvokesFunctionAsync()
|
|
{
|
|
// Arrange
|
|
int callCount = 0;
|
|
var resource = new AgentInlineSkillResource("dynamic", () =>
|
|
{
|
|
callCount++;
|
|
return "computed";
|
|
});
|
|
|
|
// Act
|
|
var result = await resource.ReadAsync();
|
|
|
|
// Assert
|
|
Assert.Equal("computed", result?.ToString());
|
|
Assert.Equal(1, callCount);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ReadAsync_Delegate_InvokesEachTimeAsync()
|
|
{
|
|
// Arrange
|
|
int callCount = 0;
|
|
var resource = new AgentInlineSkillResource("counter", () => ++callCount);
|
|
|
|
// Act
|
|
await resource.ReadAsync();
|
|
await resource.ReadAsync();
|
|
var result = await resource.ReadAsync();
|
|
|
|
// Assert
|
|
Assert.Equal(3, callCount);
|
|
}
|
|
|
|
[Fact]
|
|
public void Constructor_StaticValue_SetsNameAndDescription()
|
|
{
|
|
// Arrange & Act
|
|
var resource = new AgentInlineSkillResource("my-res", "val", "A description.");
|
|
|
|
// Assert
|
|
Assert.Equal("my-res", resource.Name);
|
|
Assert.Equal("A description.", resource.Description);
|
|
}
|
|
|
|
[Fact]
|
|
public void Constructor_StaticValue_NullDescription_DescriptionIsNull()
|
|
{
|
|
// Arrange & Act
|
|
var resource = new AgentInlineSkillResource("my-res", "val");
|
|
|
|
// Assert
|
|
Assert.Null(resource.Description);
|
|
}
|
|
|
|
[Fact]
|
|
public void Constructor_StaticValue_NullValue_Throws()
|
|
{
|
|
// Act & Assert — cast needed to target the object overload
|
|
#pragma warning disable IDE0004
|
|
Assert.Throws<ArgumentNullException>(() =>
|
|
new AgentInlineSkillResource("my-res", (object)null!));
|
|
#pragma warning restore IDE0004
|
|
}
|
|
|
|
[Fact]
|
|
public void Constructor_Delegate_NullMethod_Throws()
|
|
{
|
|
// Act & Assert
|
|
Assert.Throws<ArgumentNullException>(() =>
|
|
new AgentInlineSkillResource("my-res", null!));
|
|
}
|
|
|
|
[Fact]
|
|
public void Constructor_NullName_Throws()
|
|
{
|
|
// Act & Assert
|
|
Assert.Throws<ArgumentNullException>(() =>
|
|
new AgentInlineSkillResource(null!, "val"));
|
|
}
|
|
|
|
[Fact]
|
|
public void Constructor_WhitespaceName_Throws()
|
|
{
|
|
// Act & Assert
|
|
Assert.Throws<ArgumentException>(() =>
|
|
new AgentInlineSkillResource(" ", "val"));
|
|
}
|
|
|
|
[Fact]
|
|
public void Constructor_Delegate_SetsNameAndDescription()
|
|
{
|
|
// Arrange & Act
|
|
var resource = new AgentInlineSkillResource("dyn-res", () => "hello", "Dynamic resource.");
|
|
|
|
// Assert
|
|
Assert.Equal("dyn-res", resource.Name);
|
|
Assert.Equal("Dynamic resource.", resource.Description);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ReadAsync_WithSerializerOptions_SerializesReturnCustomTypeAsync()
|
|
{
|
|
// Arrange — delegate resource returns a custom type; the JSO includes a source-generated context for it
|
|
var jso = SkillTestJsonContext.Default.Options;
|
|
var resource = new AgentInlineSkillResource("config", () => new SkillConfig { Theme = "dark", Verbose = true }, serializerOptions: jso);
|
|
|
|
// Act
|
|
var result = await resource.ReadAsync();
|
|
|
|
// Assert — the custom type was returned successfully
|
|
Assert.NotNull(result);
|
|
Assert.Contains("dark", result!.ToString()!);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ReadAsync_SupportsCancellationTokenAsync()
|
|
{
|
|
// Arrange
|
|
using var cts = new CancellationTokenSource();
|
|
var resource = new AgentInlineSkillResource("cancellable", "value");
|
|
|
|
// Act — should not throw with a non-cancelled token
|
|
var result = await resource.ReadAsync(cancellationToken: cts.Token);
|
|
|
|
// Assert
|
|
Assert.Equal("value", result);
|
|
}
|
|
|
|
[Fact]
|
|
public void Constructor_MethodInfo_SetsNameAndDescription()
|
|
{
|
|
// Arrange
|
|
var method = typeof(AgentInlineSkillResourceTests).GetMethod(nameof(StaticResourceHelper), BindingFlags.NonPublic | BindingFlags.Static)!;
|
|
|
|
// Act
|
|
var resource = new AgentInlineSkillResource("method-resource", method, target: null, description: "A method resource.");
|
|
|
|
// Assert
|
|
Assert.Equal("method-resource", resource.Name);
|
|
Assert.Equal("A method resource.", resource.Description);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ReadAsync_MethodInfo_StaticMethod_ReturnsValueAsync()
|
|
{
|
|
// Arrange
|
|
var method = typeof(AgentInlineSkillResourceTests).GetMethod(nameof(StaticResourceHelper), BindingFlags.NonPublic | BindingFlags.Static)!;
|
|
var resource = new AgentInlineSkillResource("static-method-res", method, target: null);
|
|
|
|
// Act
|
|
var result = await resource.ReadAsync();
|
|
|
|
// Assert
|
|
Assert.Equal("static-resource-value", result?.ToString());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ReadAsync_MethodInfo_InstanceMethod_ReturnsValueAsync()
|
|
{
|
|
// Arrange
|
|
var method = typeof(AgentInlineSkillResourceTests).GetMethod(nameof(InstanceResourceHelper), BindingFlags.NonPublic | BindingFlags.Instance)!;
|
|
var resource = new AgentInlineSkillResource("instance-method-res", method, target: this);
|
|
|
|
// Act
|
|
var result = await resource.ReadAsync();
|
|
|
|
// Assert
|
|
Assert.Equal("instance-resource-value", result?.ToString());
|
|
}
|
|
|
|
[Fact]
|
|
public void Constructor_MethodInfo_NullMethod_Throws()
|
|
{
|
|
// Act & Assert
|
|
Assert.Throws<ArgumentNullException>(() =>
|
|
new AgentInlineSkillResource("my-res", null!, target: null));
|
|
}
|
|
|
|
private static string StaticResourceHelper() => "static-resource-value";
|
|
|
|
private string InstanceResourceHelper() => "instance-resource-value";
|
|
}
|