feat(a2a): per-caller SDK servers behind a key filter, cards and push receiver
This commit is contained in:
56
tests/Volcanic.AgenticCore.A2A.Server.Tests/CardTests.cs
Normal file
56
tests/Volcanic.AgenticCore.A2A.Server.Tests/CardTests.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Volcanic.AgenticCore.A2A.Server.Callers;
|
||||
using Volcanic.AgenticCore.A2A.Server.Hosting;
|
||||
|
||||
namespace Volcanic.AgenticCore.A2A.Server.Tests;
|
||||
|
||||
public sealed class CardTests : IDisposable
|
||||
{
|
||||
private readonly string _ws = Path.Combine(Path.GetTempPath(), "a2a-card", Guid.NewGuid().ToString("N"));
|
||||
private A2ACaller Caller => new("x", "isolated", _ws, "p", "1");
|
||||
private static A2ACardProvider Cards(string? publicUrl = null) =>
|
||||
new(Options.Create(new A2AHostOptions { PublicUrl = publicUrl }), NullLogger<A2ACardProvider>.Instance);
|
||||
|
||||
public CardTests() => Directory.CreateDirectory(_ws);
|
||||
|
||||
[Fact]
|
||||
public void Card_Public_IsGeneric_WithInterfacesAndApiKeyScheme()
|
||||
{
|
||||
var card = Cards("https://bot.example").Public(null);
|
||||
Assert.Equal("Volcanic assistant", card.Name);
|
||||
Assert.Contains(card.SupportedInterfaces, i => i.Url == "https://bot.example/a2a" && i.ProtocolBinding == "JSONRPC");
|
||||
Assert.Contains(card.SupportedInterfaces, i => i.Url == "https://bot.example/a2a/v1" && i.ProtocolBinding == "HTTP+JSON");
|
||||
Assert.True(card.Capabilities.ExtendedAgentCard);
|
||||
Assert.Equal("X-API-Key", card.SecuritySchemes!["apiKey"].ApiKeySecurityScheme!.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Card_Public_UsesRequestBaseUrlWithoutPublicUrl() =>
|
||||
Assert.Equal("http://localhost:5000/a2a", Cards().Public("http://localhost:5000").SupportedInterfaces[0].Url);
|
||||
|
||||
[Fact]
|
||||
public void Card_Extended_FromCardJson()
|
||||
{
|
||||
Directory.CreateDirectory(Path.Combine(_ws, ".volcanic", "a2a"));
|
||||
File.WriteAllText(A2ACardProvider.CardPath(_ws), """
|
||||
{ "name": "Eugene's assistant", "description": "Schedules meetings.",
|
||||
"skills": [ { "id": "scheduling", "name": "Scheduling", "description": "Books slots", "tags": ["calendar"], "examples": ["book 30 min"] } ] }
|
||||
""");
|
||||
var card = Cards().Extended(Caller, null);
|
||||
Assert.Equal("Eugene's assistant", card.Name);
|
||||
Assert.Equal("scheduling", Assert.Single(card.Skills).Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Card_Extended_FallsBackToPersona()
|
||||
{
|
||||
File.WriteAllText(Path.Combine(_ws, "PERSONA.md"), "\n# I am Eugene's secretary\nmore");
|
||||
var card = Cards().Extended(Caller, null);
|
||||
Assert.Equal("Volcanic assistant", card.Name);
|
||||
Assert.Equal("I am Eugene's secretary", card.Description);
|
||||
Assert.Equal("chat", Assert.Single(card.Skills).Id);
|
||||
}
|
||||
|
||||
public void Dispose() { try { Directory.Delete(_ws, true); } catch (IOException) { } }
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using System.Net;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Volcanic.AgenticCore.A2A.Server.Push;
|
||||
|
||||
namespace Volcanic.AgenticCore.A2A.Server.Tests;
|
||||
|
||||
public class PushUrlGuardTests
|
||||
{
|
||||
private static PushUrlGuard Guard(bool allowPrivate = false, params string[] resolved) =>
|
||||
new(Options.Create(new A2AHostOptions { AllowPrivatePushUrls = allowPrivate }),
|
||||
(_, _) => Task.FromResult(resolved.Select(IPAddress.Parse).ToArray()));
|
||||
|
||||
[Theory]
|
||||
[InlineData("http://hooks.example/x", "must be https")]
|
||||
[InlineData("https://127.0.0.1/x", "private")]
|
||||
[InlineData("https://10.1.2.3/x", "private")]
|
||||
[InlineData("https://100.64.0.7/x", "private")]
|
||||
[InlineData("not a url", "absolute")]
|
||||
public async Task Push_PrivateOrPlainUrl_Rejected(string url, string expected) =>
|
||||
Assert.Contains(expected, await Guard(false, "93.184.216.34").ProblemAsync(url, default));
|
||||
|
||||
[Fact]
|
||||
public async Task Push_HostResolvingToPrivate_Rejected() =>
|
||||
Assert.Contains("private", await Guard(false, "192.168.1.5").ProblemAsync("https://hooks.example/x", default));
|
||||
|
||||
[Fact]
|
||||
public async Task Push_PublicHttps_Accepted() =>
|
||||
Assert.Null(await Guard(false, "93.184.216.34").ProblemAsync("https://hooks.example/x", default));
|
||||
|
||||
[Fact]
|
||||
public async Task Push_PrivateAllowed_Accepted() =>
|
||||
Assert.Null(await Guard(true).ProblemAsync("http://127.0.0.1:5000/x", default));
|
||||
}
|
||||
Reference in New Issue
Block a user