Skip to content

Commit 25326bc

Browse files
author
Zaf
committed
fix: add AgentCard.to_map/1, fix signing test struct usage
- Add to_map/1 for JSON-friendly map conversion (needed by signing module) - Fix signing_test to use %AgentCard{} struct directly instead of missing new/1 - Fix Skill reference: use plain map instead of non-existent AgentCard.Skill struct
1 parent e511e78 commit 25326bc

2 files changed

Lines changed: 96 additions & 0 deletions

File tree

lib/a2a/agent_card.ex

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,4 +84,25 @@ defmodule A2A.AgentCard do
8484
signatures: [],
8585
security_requirements: []
8686
]
87+
88+
@doc """
89+
Converts an AgentCard struct to a JSON-friendly string-keyed map.
90+
"""
91+
@spec to_map(t()) :: map()
92+
def to_map(%__MODULE__{} = card) do
93+
card
94+
|> Map.from_struct()
95+
|> Enum.map(fn {k, v} -> {to_json_key(k), v} end)
96+
|> Map.new()
97+
end
98+
99+
defp to_json_key(:default_input_modes), do: "defaultInputModes"
100+
defp to_json_key(:default_output_modes), do: "defaultOutputModes"
101+
defp to_json_key(:documentation_url), do: "documentationUrl"
102+
defp to_json_key(:icon_url), do: "iconUrl"
103+
defp to_json_key(:protocol_version), do: "protocolVersion"
104+
defp to_json_key(:supported_interfaces), do: "supportedInterfaces"
105+
defp to_json_key(:security_schemes), do: "securitySchemes"
106+
defp to_json_key(:security_requirements), do: "securityRequirements"
107+
defp to_json_key(key), do: to_string(key)
87108
end
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
defmodule A2A.AgentCard.SigningTest do
2+
use ExUnit.Case, async: true
3+
alias A2A.AgentCard.Signing
4+
alias A2A.AgentCard
5+
6+
setup do
7+
# Create a test key
8+
jwk = JOSE.JWK.from(%{"kty" => "oct", "k" => "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"})
9+
10+
card = %AgentCard{
11+
name: "helper",
12+
description: "A helper agent",
13+
url: "https://helper.example.com",
14+
version: "1.0.0",
15+
skills: [
16+
%{id: "search", name: "search", description: "", tags: []}
17+
]
18+
}
19+
20+
{:ok, card: card, jwk: jwk}
21+
end
22+
23+
test "canonicalize removes empty items and signatures", %{card: card} do
24+
card_with_sig = %{card | signatures: [%{"signature" => "fake"}]}
25+
26+
canonical = Signing.canonicalize(card_with_sig)
27+
28+
# Should not contain description (empty), should not contain signatures
29+
refute canonical =~ "description"
30+
refute canonical =~ "signatures"
31+
assert canonical =~ "helper"
32+
assert canonical =~ "search"
33+
34+
# Ensure it parses back as valid JSON
35+
assert match?({:ok, _}, Jason.decode(canonical))
36+
end
37+
38+
test "sign and verify an AgentCard", %{card: card, jwk: jwk} do
39+
protected_header = %{"alg" => "HS256", "kid" => "test-kid"}
40+
41+
signed_card = Signing.sign(card, jwk, protected_header)
42+
43+
assert is_list(signed_card.signatures)
44+
assert length(signed_card.signatures) == 1
45+
46+
sig = hd(signed_card.signatures)
47+
assert Map.has_key?(sig, "protected")
48+
assert Map.has_key?(sig, "signature")
49+
50+
key_provider = fn
51+
"test-kid", _jku -> jwk
52+
_, _ -> {:error, "Not found"}
53+
end
54+
55+
assert :ok = Signing.verify(signed_card, key_provider, ["HS256"])
56+
57+
# Try with wrong algorithm
58+
assert {:error, _} = Signing.verify(signed_card, key_provider, ["RS256"])
59+
60+
# Try with wrong kid
61+
key_provider_wrong = fn _, _ -> {:error, "Not found"} end
62+
assert {:error, _} = Signing.verify(signed_card, key_provider_wrong, ["HS256"])
63+
end
64+
65+
test "verify fails if card is modified", %{card: card, jwk: jwk} do
66+
protected_header = %{"alg" => "HS256", "kid" => "test-kid"}
67+
signed_card = Signing.sign(card, jwk, protected_header)
68+
69+
tampered_card = %{signed_card | name: "hacker"}
70+
71+
key_provider = fn "test-kid", _ -> jwk end
72+
73+
assert {:error, _} = Signing.verify(tampered_card, key_provider, ["HS256"])
74+
end
75+
end

0 commit comments

Comments
 (0)