Files
VolcanicAgenticCore/tests/Volcanic.AgenticCore.Extensions.Tests/A2A/A2AResultsTests.cs

106 lines
4.5 KiB
C#

using System.Text;
using A2A;
using Volcanic.AgenticCore.Extensions.A2A;
using A2AMessage = global::A2A.Message;
using A2ATaskStatus = global::A2A.TaskStatus;
namespace Volcanic.AgenticCore.Extensions.Tests.A2A;
public sealed class A2AResultsTests : IDisposable
{
private readonly string _ws = Path.Combine(Path.GetTempPath(), "a2a-results", Guid.NewGuid().ToString("N"));
private static AgentTask Task_(TaskState state, string? statusText = null, params Artifact[] artifacts) => new()
{
Id = "task-123456789", ContextId = "ctx-1",
Status = new A2ATaskStatus
{
State = state,
Message = statusText is null ? null : new A2AMessage { Role = Role.Agent, MessageId = "m", Parts = [Part.FromText(statusText)] },
},
Artifacts = [.. artifacts],
};
[Fact]
public void FinalText_PrefersResponseArtifact()
{
var task = Task_(TaskState.Completed, null,
new Artifact { ArtifactId = "other", Parts = [Part.FromText("side")] },
new Artifact { ArtifactId = "response", Parts = [Part.FromText("Booked "), Part.FromText("Friday")] });
Assert.Equal("Booked Friday", A2AResults.FinalText(task));
}
[Fact]
public void FinalText_FallsBackToTextArtifactsThenStatus()
{
Assert.Equal("side", A2AResults.FinalText(Task_(TaskState.Completed, null, new Artifact { ArtifactId = "x", Parts = [Part.FromText("side")] })));
Assert.Equal("why", A2AResults.FinalText(Task_(TaskState.Failed, "why")));
}
[Fact]
public void Send_FileArtifact_SavedToInbox()
{
var task = Task_(TaskState.Completed, null, new Artifact
{
ArtifactId = "f1", Name = "plan.pdf", Parts = [Part.FromRaw(Encoding.UTF8.GetBytes("pdf!"), "application/pdf", "../../plan.pdf")],
});
var files = A2AResults.SaveFiles(_ws, "travel-bot", task, 1024);
var file = Assert.Single(files.Saved);
Assert.Equal("inbox/a2a/travel-bot/plan.pdf", file);
Assert.Equal("pdf!", File.ReadAllText(Path.Combine(_ws, file)));
}
[Fact]
public void Describe_InputRequired_HasQuestionAndHint()
{
var text = A2AResults.Describe("travel-bot", Task_(TaskState.InputRequired, "Which date?"), []);
Assert.Contains("input-required", text);
Assert.Contains("Which date?", text);
Assert.Contains("action=send", text);
Assert.Contains("task-123456789", text);
Assert.Contains("ctx-1", text);
}
[Fact]
public void DeliveryText_HasHeader()
{
var text = A2AResults.DeliveryText("travel-bot", Task_(TaskState.Completed, null,
new Artifact { ArtifactId = "response", Parts = [Part.FromText("Done")] }), ["inbox/a2a/travel-bot/x.pdf"]);
Assert.StartsWith("[A2A result from \"travel-bot\", task task-123456789 (completed)]", text);
Assert.Contains("Done", text);
Assert.Contains("Files: inbox/a2a/travel-bot/x.pdf", text);
}
[Fact]
public void Describe_InputRequired_QuestionComesBeforeStaleResponseArtifact()
{
var task = Task_(TaskState.InputRequired, "Which date?",
new Artifact { ArtifactId = "response", Parts = [Part.FromText("old answer")] });
var text = A2AResults.Describe("travel-bot", task, []);
Assert.True(text.IndexOf("Which date?", StringComparison.Ordinal) < text.IndexOf("old answer", StringComparison.Ordinal));
}
[Fact]
public void SaveFiles_DotDotName_BecomesFile_AndControlCharsStripped()
{
var task = Task_(TaskState.Completed, null,
new Artifact { ArtifactId = "a", Parts = [Part.FromRaw(Encoding.UTF8.GetBytes("x"), "text/plain", "..")] },
new Artifact { ArtifactId = "b", Parts = [Part.FromRaw(Encoding.UTF8.GetBytes("y"), "text/plain", "a\u0001b.txt")] });
var files = A2AResults.SaveFiles(_ws, "bot", task, 1024).Saved;
Assert.Equal(["inbox/a2a/bot/file", "inbox/a2a/bot/ab.txt"], files);
}
[Fact]
public void SaveFiles_ReportsOversizedParts_AndDescribeListsThem()
{
var task = Task_(TaskState.Completed, null,
new Artifact { ArtifactId = "a", Parts = [Part.FromRaw(new byte[100], "text/plain", "big.bin")] });
var result = A2AResults.SaveFiles(_ws, "bot", task, 10);
Assert.Empty(result.Saved);
Assert.Equal(["big.bin"], result.Skipped.Names);
Assert.Contains("Files skipped (over 10 bytes): big.bin", A2AResults.Describe("bot", task, result.Saved, result.Skipped));
}
public void Dispose() { try { Directory.Delete(_ws, true); } catch (IOException) { } }
}