Files
agent-framework/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/AgentInlineSkillResourceTests.cs
SergeyMenshykh e5f7b9c260 .NET: Support reflection for discovery of resources and scripts in class-based skills (#5183)
* 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>
2026-04-10 11:56:28 +01:00

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";
}