// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.Text.Json;
using Xunit;
namespace Microsoft.Extensions.AI.Contents;
public class ToolApprovalResponseContentTests
{
[Fact]
public void Constructor_InvalidArguments_Throws()
{
Assert.Throws<ArgumentNullException>("requestId", () => new ToolApprovalResponseContent(null!, true, new FunctionCallContent("FCC1", "TestFunction")));
Assert.Throws<ArgumentException>("requestId", () => new ToolApprovalResponseContent("", true, new FunctionCallContent("FCC1", "TestFunction")));
Assert.Throws<ArgumentException>("requestId", () => new ToolApprovalResponseContent("\r\t\n ", true, new FunctionCallContent("FCC1", "TestFunction")));
Assert.Throws<ArgumentNullException>("toolCall", () => new ToolApprovalResponseContent("id", true, null!));
}
public static TheoryData<ToolCallContent> ToolCallContentInstances => new()
{
new FunctionCallContent("FCC1", "TestFunction", new Dictionary<string, object?> { { "param1", 123 } }),
new McpServerToolCallContent("MCC1", "TestTool", "TestServer") { Arguments = new Dictionary<string, object?> { { "arg1", "value1" } } },
new CodeInterpreterToolCallContent("CI1") { Inputs = [new DataContent("print('hello')"u8.ToArray(), "text/x-python")] },
new ImageGenerationToolCallContent("IG1"),
};
[Theory]
[MemberData(nameof(ToolCallContentInstances), DisableDiscoveryEnumeration = true)]
public void Constructor_Roundtrips(ToolCallContent toolCall)
{
ToolApprovalResponseContent content = new("req-1", true, toolCall);
Assert.Equal("req-1", content.RequestId);
Assert.True(content.Approved);
Assert.Same(toolCall, content.ToolCall);
content = new("req-2", false, toolCall);
Assert.Equal("req-2", content.RequestId);
Assert.False(content.Approved);
Assert.Same(toolCall, content.ToolCall);
}
[Theory]
[MemberData(nameof(ToolCallContentInstances), DisableDiscoveryEnumeration = true)]
public void Serialization_Roundtrips(ToolCallContent toolCall)
{
var content = new ToolApprovalResponseContent("request123", true, toolCall)
{
Reason = "Approved for testing"
};
AssertSerializationRoundtrips<ToolApprovalResponseContent>(content);
AssertSerializationRoundtrips<InputResponseContent>(content);
AssertSerializationRoundtrips<AIContent>(content);
static void AssertSerializationRoundtrips<T>(ToolApprovalResponseContent content)
where T : AIContent
{
T contentAsT = (T)(object)content;
string json = JsonSerializer.Serialize(contentAsT, AIJsonUtilities.DefaultOptions);
T? deserialized = JsonSerializer.Deserialize<T>(json, AIJsonUtilities.DefaultOptions);
Assert.NotNull(deserialized);
var deserializedContent = Assert.IsType<ToolApprovalResponseContent>(deserialized);
Assert.Equal(content.RequestId, deserializedContent.RequestId);
Assert.Equal(content.Approved, deserializedContent.Approved);
Assert.Equal(content.Reason, deserializedContent.Reason);
Assert.NotNull(deserializedContent.ToolCall);
Assert.IsType(content.ToolCall.GetType(), deserializedContent.ToolCall);
Assert.Equal(content.ToolCall.CallId, deserializedContent.ToolCall.CallId);
}
}
[Theory]
[InlineData(null)]
[InlineData("Custom rejection reason")]
public void Serialization_WithReason_Roundtrips(string? reason)
{
var content = new ToolApprovalResponseContent("request123", true, new FunctionCallContent("call123", "functionName"))
{
Reason = reason
};
var json = JsonSerializer.Serialize(content, AIJsonUtilities.DefaultOptions);
var deserializedContent = JsonSerializer.Deserialize<ToolApprovalResponseContent>(json, AIJsonUtilities.DefaultOptions);
Assert.NotNull(deserializedContent);
Assert.Equal(content.RequestId, deserializedContent.RequestId);
Assert.Equal(content.Approved, deserializedContent.Approved);
Assert.Equal(content.Reason, deserializedContent.Reason);
Assert.NotNull(deserializedContent.ToolCall);
var functionCall = Assert.IsType<FunctionCallContent>(deserializedContent.ToolCall);
Assert.Equal(content.ToolCall.CallId, functionCall.CallId);
Assert.Equal(((FunctionCallContent)content.ToolCall).Name, functionCall.Name);
}
[Fact]
public void JsonDeserialization_KnownPayload()
{
const string Json = """
{
"$type": "toolApprovalResponse",
"requestId": "req-abc123",
"approved": true,
"toolCall": {
"$type": "functionCall",
"callId": "call1",
"name": "myFunc"
},
"reason": "Looks safe",
"additionalProperties": {
"key": "val"
}
}
""";
AIContent? result = JsonSerializer.Deserialize<AIContent>(Json, AIJsonUtilities.DefaultOptions);
Assert.NotNull(result);
var approvalResponse = Assert.IsType<ToolApprovalResponseContent>(result);
Assert.Equal("req-abc123", approvalResponse.RequestId);
Assert.True(approvalResponse.Approved);
Assert.NotNull(approvalResponse.ToolCall);
var funcCall = Assert.IsType<FunctionCallContent>(approvalResponse.ToolCall);
Assert.Equal("call1", funcCall.CallId);
Assert.Equal("myFunc", funcCall.Name);
Assert.Equal("Looks safe", approvalResponse.Reason);
Assert.NotNull(approvalResponse.AdditionalProperties);
Assert.Equal("val", approvalResponse.AdditionalProperties["key"]?.ToString());
}
}