a2a-test-framework 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/a2a.json +1961 -0
- data/a2a.proto +796 -0
- data/endpoints/grpc/cancel_task.json +10 -0
- data/endpoints/grpc/create_task_push_notification_config.json +10 -0
- data/endpoints/grpc/delete_task_push_notification_config.json +10 -0
- data/endpoints/grpc/get_extended_agent_card.json +10 -0
- data/endpoints/grpc/get_task.json +10 -0
- data/endpoints/grpc/get_task_push_notification_config.json +10 -0
- data/endpoints/grpc/list_task_push_notification_configs.json +10 -0
- data/endpoints/grpc/list_tasks.json +10 -0
- data/endpoints/grpc/send_message.json +10 -0
- data/endpoints/grpc/send_streaming_message.json +10 -0
- data/endpoints/grpc/subscribe_to_task.json +10 -0
- data/endpoints/rest/cancel_task.json +85 -0
- data/endpoints/rest/create_task_push_notification_config.json +104 -0
- data/endpoints/rest/delete_task_push_notification_config.json +46 -0
- data/endpoints/rest/get_extended_agent_card.json +168 -0
- data/endpoints/rest/get_task.json +111 -0
- data/endpoints/rest/get_task_push_notification_config.json +90 -0
- data/endpoints/rest/list_task_push_notification_configs.json +108 -0
- data/endpoints/rest/list_tasks.json +239 -0
- data/endpoints/rest/send_message.json +57 -0
- data/endpoints/rest/send_streaming_message.json +75 -0
- data/endpoints/rest/subscribe_to_task.json +68 -0
- data/exe/a2a-test +6 -0
- data/lib/a2a_test_framework/cli.rb +190 -0
- data/lib/a2a_test_framework/sse_client.rb +104 -0
- data/lib/a2a_test_framework/test_helper.rb +146 -0
- data/lib/a2a_test_framework/version.rb +5 -0
- data/lib/a2a_test_framework.rb +17 -0
- data/tests/grpc/cancel_task_test.rb +69 -0
- data/tests/grpc/create_task_push_notification_config_test.rb +79 -0
- data/tests/grpc/delete_task_push_notification_config_test.rb +54 -0
- data/tests/grpc/error_code_mappings_test.rb +39 -0
- data/tests/grpc/error_handling_test.rb +175 -0
- data/tests/grpc/get_extended_agent_card_test.rb +83 -0
- data/tests/grpc/get_task_push_notification_config_test.rb +39 -0
- data/tests/grpc/get_task_test.rb +76 -0
- data/tests/grpc/grpc_binding_test.rb +74 -0
- data/tests/grpc/list_task_push_notification_configs_test.rb +53 -0
- data/tests/grpc/list_tasks_test.rb +117 -0
- data/tests/grpc/protocol_data_model_test.rb +14 -0
- data/tests/grpc/send_message_test.rb +141 -0
- data/tests/grpc/send_streaming_message_test.rb +122 -0
- data/tests/grpc/streaming_event_delivery_test.rb +48 -0
- data/tests/grpc/subscribe_to_task_test.rb +92 -0
- data/tests/grpc/versioning_test.rb +32 -0
- data/tests/rest/agent_card_caching_test.rb +39 -0
- data/tests/rest/agent_card_signing_test.rb +74 -0
- data/tests/rest/agent_discovery_test.rb +117 -0
- data/tests/rest/authentication_authorization_test.rb +62 -0
- data/tests/rest/cancel_task_test.rb +110 -0
- data/tests/rest/capability_validation_test.rb +78 -0
- data/tests/rest/context_identifier_semantics_test.rb +75 -0
- data/tests/rest/create_task_push_notification_config_test.rb +122 -0
- data/tests/rest/custom_binding_test.rb +96 -0
- data/tests/rest/delete_task_push_notification_config_test.rb +103 -0
- data/tests/rest/error_code_mappings_test.rb +45 -0
- data/tests/rest/error_handling_test.rb +178 -0
- data/tests/rest/extension_versioning_test.rb +44 -0
- data/tests/rest/field_presence_optionality_test.rb +64 -0
- data/tests/rest/functional_equivalence_test.rb +23 -0
- data/tests/rest/get_extended_agent_card_test.rb +67 -0
- data/tests/rest/get_task_push_notification_config_test.rb +75 -0
- data/tests/rest/get_task_test.rb +134 -0
- data/tests/rest/history_length_semantics_test.rb +91 -0
- data/tests/rest/http_rest_binding_test.rb +114 -0
- data/tests/rest/iana_registrations_test.rb +47 -0
- data/tests/rest/idempotency_test.rb +69 -0
- data/tests/rest/in_task_authorization_test.rb +45 -0
- data/tests/rest/json_field_naming_test.rb +89 -0
- data/tests/rest/json_rpc_binding_test.rb +102 -0
- data/tests/rest/list_task_push_notification_configs_test.rb +92 -0
- data/tests/rest/list_tasks_test.rb +162 -0
- data/tests/rest/messages_and_artifacts_test.rb +101 -0
- data/tests/rest/multi_turn_conversation_test.rb +94 -0
- data/tests/rest/protocol_data_model_test.rb +99 -0
- data/tests/rest/protocol_security_test.rb +25 -0
- data/tests/rest/protocol_selection_negotiation_test.rb +24 -0
- data/tests/rest/push_notification_delivery_test.rb +115 -0
- data/tests/rest/security_considerations_test.rb +101 -0
- data/tests/rest/send_message_test.rb +230 -0
- data/tests/rest/send_streaming_message_test.rb +129 -0
- data/tests/rest/service_parameters_test.rb +52 -0
- data/tests/rest/streaming_event_delivery_test.rb +58 -0
- data/tests/rest/subscribe_to_task_test.rb +99 -0
- data/tests/rest/task_identifier_semantics_test.rb +67 -0
- data/tests/rest/timestamps_test.rb +70 -0
- data/tests/rest/versioning_responsibilities_test.rb +46 -0
- data/tests/rest/versioning_test.rb +44 -0
- metadata +159 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
require "a2a_test_framework/test_helper"
|
|
2
|
+
|
|
3
|
+
# REST endpoint: GET /tasks
|
|
4
|
+
# Request: ListTasksRequest (contextId, status, historyLength, includeArtifacts, pageSize, pageToken, statusTimestampAfter, tenant)
|
|
5
|
+
# Response: ListTasksResponse (tasks[], nextPageToken, pageSize, totalSize)
|
|
6
|
+
|
|
7
|
+
describe "GET /tasks" do
|
|
8
|
+
# --- Authorization Scoping ---
|
|
9
|
+
# NOTE: Commented out -- reference server does not implement authentication
|
|
10
|
+
|
|
11
|
+
# describe "when an authenticated client sends a ListTasks request" do
|
|
12
|
+
# it "should only return tasks visible to the authenticated client" do
|
|
13
|
+
# end
|
|
14
|
+
#
|
|
15
|
+
# it "should not return tasks belonging to other clients" do
|
|
16
|
+
# end
|
|
17
|
+
# end
|
|
18
|
+
|
|
19
|
+
# describe "when an unauthenticated client sends a ListTasks request" do
|
|
20
|
+
# it "should respond with an authentication error" do
|
|
21
|
+
# end
|
|
22
|
+
# end
|
|
23
|
+
|
|
24
|
+
# --- includeArtifacts Field Behavior ---
|
|
25
|
+
|
|
26
|
+
describe "when a client sends a ListTasks request without setting includeArtifacts" do
|
|
27
|
+
it "should omit the artifacts field from each Task object" do
|
|
28
|
+
create_task!(text: "Artifacts omit test")
|
|
29
|
+
response = http_get("/tasks")
|
|
30
|
+
response.code.to_i.should.equal 200
|
|
31
|
+
|
|
32
|
+
data = parse_json(response)
|
|
33
|
+
data["tasks"].should.be.kind_of Array
|
|
34
|
+
data["tasks"].length.should.be > 0
|
|
35
|
+
|
|
36
|
+
# Without includeArtifacts, artifacts should be omitted
|
|
37
|
+
task = data["tasks"].first
|
|
38
|
+
task.key?("artifacts").should.equal false
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
describe "when a client sends a ListTasks request with includeArtifacts set to true" do
|
|
43
|
+
it "should include the artifacts field in each Task" do
|
|
44
|
+
create_task!(text: "Artifacts include test")
|
|
45
|
+
response = http_get("/tasks?includeArtifacts=true")
|
|
46
|
+
response.code.to_i.should.equal 200
|
|
47
|
+
|
|
48
|
+
data = parse_json(response)
|
|
49
|
+
task = data["tasks"].first
|
|
50
|
+
task.key?("artifacts").should.equal true
|
|
51
|
+
task["artifacts"].should.be.kind_of Array
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# --- nextPageToken Field ---
|
|
56
|
+
|
|
57
|
+
describe "when a client receives a ListTasks response" do
|
|
58
|
+
it "should contain a nextPageToken field" do
|
|
59
|
+
create_task!(text: "Page token test")
|
|
60
|
+
response = http_get("/tasks")
|
|
61
|
+
data = parse_json(response)
|
|
62
|
+
|
|
63
|
+
data.key?("nextPageToken").should.equal true
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
describe "when all tasks fit within a single page" do
|
|
68
|
+
it "should set nextPageToken to an empty string" do
|
|
69
|
+
# Use a large page size to fit all
|
|
70
|
+
response = http_get("/tasks?pageSize=100")
|
|
71
|
+
data = parse_json(response)
|
|
72
|
+
|
|
73
|
+
if data["tasks"].length < 100
|
|
74
|
+
data["nextPageToken"].should.equal ""
|
|
75
|
+
end
|
|
76
|
+
true.should.equal true
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# --- Cursor-Based Pagination ---
|
|
81
|
+
|
|
82
|
+
describe "when using pagination" do
|
|
83
|
+
it "should respect pageSize parameter" do
|
|
84
|
+
3.times { |i| create_task!(text: "Page size test #{i}") }
|
|
85
|
+
|
|
86
|
+
response = http_get("/tasks?pageSize=2")
|
|
87
|
+
response.code.to_i.should.equal 200
|
|
88
|
+
|
|
89
|
+
data = parse_json(response)
|
|
90
|
+
data["tasks"].length.should.be <= 2
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it "should include pageSize in the response" do
|
|
94
|
+
response = http_get("/tasks?pageSize=5")
|
|
95
|
+
data = parse_json(response)
|
|
96
|
+
|
|
97
|
+
data["pageSize"].should.not.be.nil
|
|
98
|
+
data["pageSize"].should.be > 0
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
it "should include totalSize in the response" do
|
|
102
|
+
create_task!(text: "Total size test")
|
|
103
|
+
response = http_get("/tasks")
|
|
104
|
+
data = parse_json(response)
|
|
105
|
+
|
|
106
|
+
data["totalSize"].should.not.be.nil
|
|
107
|
+
data["totalSize"].should.be >= 1
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
it "should return the first page when no pageToken is provided" do
|
|
111
|
+
response = http_get("/tasks?pageSize=2")
|
|
112
|
+
response.code.to_i.should.equal 200
|
|
113
|
+
|
|
114
|
+
data = parse_json(response)
|
|
115
|
+
data["tasks"].should.be.kind_of Array
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# --- Filtering ---
|
|
120
|
+
|
|
121
|
+
describe "when a client filters tasks by contextId" do
|
|
122
|
+
it "should only return tasks matching that contextId" do
|
|
123
|
+
task = create_task!(text: "Context filter test")
|
|
124
|
+
context_id = task["contextId"]
|
|
125
|
+
|
|
126
|
+
response = http_get("/tasks?contextId=#{context_id}")
|
|
127
|
+
response.code.to_i.should.equal 200
|
|
128
|
+
|
|
129
|
+
data = parse_json(response)
|
|
130
|
+
data["tasks"].each do |t|
|
|
131
|
+
t["contextId"].should.equal context_id
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
it "should return empty list for non-existent contextId" do
|
|
136
|
+
response = http_get("/tasks?contextId=nonexistent-#{SecureRandom.uuid}")
|
|
137
|
+
response.code.to_i.should.equal 200
|
|
138
|
+
|
|
139
|
+
data = parse_json(response)
|
|
140
|
+
data["tasks"].length.should.equal 0
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# --- Ordering ---
|
|
145
|
+
# NOTE: Commented out -- ordering behavior depends on server implementation
|
|
146
|
+
|
|
147
|
+
# describe "when tasks are returned in the response" do
|
|
148
|
+
# it "should sort tasks by status timestamp in descending order" do
|
|
149
|
+
# end
|
|
150
|
+
#
|
|
151
|
+
# it "should place the most recently updated task first" do
|
|
152
|
+
# end
|
|
153
|
+
# end
|
|
154
|
+
|
|
155
|
+
# describe "when paginating through all results" do
|
|
156
|
+
# it "should maintain consistent ordering across pages" do
|
|
157
|
+
# end
|
|
158
|
+
#
|
|
159
|
+
# it "should not have a later-page task with a more recent update than earlier-page tasks" do
|
|
160
|
+
# end
|
|
161
|
+
# end
|
|
162
|
+
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
require "a2a_test_framework/test_helper"
|
|
2
|
+
|
|
3
|
+
# Cross-cutting: Messages and artifacts semantics
|
|
4
|
+
|
|
5
|
+
describe "Messages and Artifacts (REST)" do
|
|
6
|
+
# --- Message/Artifact Separation ---
|
|
7
|
+
|
|
8
|
+
describe "when a task produces output results" do
|
|
9
|
+
it "should return results using Artifacts associated with the Task" do
|
|
10
|
+
task = create_task!(text: "Artifact output test")
|
|
11
|
+
response = http_get("/tasks/#{task["id"]}")
|
|
12
|
+
data = parse_json(response)
|
|
13
|
+
|
|
14
|
+
# The echo agent produces artifacts
|
|
15
|
+
data["artifacts"].should.not.be.nil
|
|
16
|
+
data["artifacts"].should.be.kind_of Array
|
|
17
|
+
data["artifacts"].length.should.be > 0
|
|
18
|
+
|
|
19
|
+
artifact = data["artifacts"].first
|
|
20
|
+
artifact["parts"].should.be.kind_of Array
|
|
21
|
+
artifact["parts"].length.should.be > 0
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
describe "when examining artifact structure" do
|
|
26
|
+
it "should include artifactId for each artifact" do
|
|
27
|
+
task = create_task!(text: "ArtifactId test")
|
|
28
|
+
response = http_get("/tasks/#{task["id"]}")
|
|
29
|
+
data = parse_json(response)
|
|
30
|
+
|
|
31
|
+
if data["artifacts"] && data["artifacts"].length > 0
|
|
32
|
+
data["artifacts"].each do |artifact|
|
|
33
|
+
artifact["artifactId"].should.not.be.nil
|
|
34
|
+
artifact["artifactId"].should.be.kind_of String
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
true.should.equal true
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it "should include parts array in each artifact" do
|
|
41
|
+
task = create_task!(text: "Artifact parts test")
|
|
42
|
+
response = http_get("/tasks/#{task["id"]}")
|
|
43
|
+
data = parse_json(response)
|
|
44
|
+
|
|
45
|
+
if data["artifacts"] && data["artifacts"].length > 0
|
|
46
|
+
data["artifacts"].each do |artifact|
|
|
47
|
+
artifact["parts"].should.be.kind_of Array
|
|
48
|
+
artifact["parts"].each do |part|
|
|
49
|
+
# Each part should have some content
|
|
50
|
+
has_content = part.key?("text") || part.key?("data") || part.key?("url") || part.key?("raw")
|
|
51
|
+
has_content.should.equal true
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
true.should.equal true
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# --- Message History ---
|
|
60
|
+
|
|
61
|
+
describe "when examining task history" do
|
|
62
|
+
it "should include both user and agent messages" do
|
|
63
|
+
task = create_task!(text: "History messages test")
|
|
64
|
+
response = http_get("/tasks/#{task["id"]}?historyLength=10")
|
|
65
|
+
data = parse_json(response)
|
|
66
|
+
|
|
67
|
+
if data["history"] && data["history"].length > 0
|
|
68
|
+
roles = data["history"].map { |m| m["role"] }
|
|
69
|
+
roles.should.include "ROLE_USER"
|
|
70
|
+
roles.should.include "ROLE_AGENT"
|
|
71
|
+
end
|
|
72
|
+
true.should.equal true
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it "should include messageId in each history message" do
|
|
76
|
+
task = create_task!(text: "Message ID history test")
|
|
77
|
+
response = http_get("/tasks/#{task["id"]}?historyLength=10")
|
|
78
|
+
data = parse_json(response)
|
|
79
|
+
|
|
80
|
+
if data["history"] && data["history"].length > 0
|
|
81
|
+
data["history"].each do |msg|
|
|
82
|
+
msg["messageId"].should.not.be.nil
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
true.should.equal true
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# --- Message Reliability ---
|
|
90
|
+
# NOTE: Commented out -- cannot test delivery guarantees in conformance suite
|
|
91
|
+
|
|
92
|
+
# describe "when a client disconnects and reconnects to a stream" do
|
|
93
|
+
# it "should not guarantee delivery of all status update messages during disconnection" do
|
|
94
|
+
# end
|
|
95
|
+
# end
|
|
96
|
+
|
|
97
|
+
# describe "when determining message persistence" do
|
|
98
|
+
# it "should let the agent determine which Messages are persisted in the Task History" do
|
|
99
|
+
# end
|
|
100
|
+
# end
|
|
101
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
require "a2a_test_framework/test_helper"
|
|
2
|
+
|
|
3
|
+
# Cross-cutting: Multi-turn conversation patterns
|
|
4
|
+
# REST: POST /message:send, POST /message:stream
|
|
5
|
+
|
|
6
|
+
describe "Multi-Turn Conversation Patterns (REST)" do
|
|
7
|
+
# --- Context Continuity ---
|
|
8
|
+
|
|
9
|
+
describe "when a client sends a message with a contextId from a previous interaction" do
|
|
10
|
+
it "should create a new task within the same context" do
|
|
11
|
+
# Create first task
|
|
12
|
+
task1 = create_task!(text: "First message in context")
|
|
13
|
+
context_id = task1["contextId"]
|
|
14
|
+
|
|
15
|
+
# Send second message with same contextId
|
|
16
|
+
body = build_send_message_request(text: "Second message", context_id: context_id)
|
|
17
|
+
response = http_post("/message:send", body)
|
|
18
|
+
response.code.to_i.should.equal 200
|
|
19
|
+
|
|
20
|
+
data = parse_json(response)
|
|
21
|
+
if data["task"]
|
|
22
|
+
data["task"]["contextId"].should.equal context_id
|
|
23
|
+
end
|
|
24
|
+
true.should.equal true
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
describe "when a client sends a message to continue a specific task" do
|
|
29
|
+
it "should add the message to the task history" do
|
|
30
|
+
# The reference server completes tasks synchronously, so sending to
|
|
31
|
+
# a completed task will error. This tests the message is accepted
|
|
32
|
+
# or properly rejected.
|
|
33
|
+
task = create_task!(text: "Original message")
|
|
34
|
+
|
|
35
|
+
body = build_send_message_request(text: "Follow up message")
|
|
36
|
+
body["message"]["taskId"] = task["id"]
|
|
37
|
+
response = http_post("/message:send", body)
|
|
38
|
+
|
|
39
|
+
# Either accepted (200) or rejected because task is terminal (4xx)
|
|
40
|
+
(response.code.to_i == 200 || response.code.to_i >= 400).should.equal true
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# --- Context Sharing ---
|
|
45
|
+
|
|
46
|
+
describe "when multiple tasks share the same contextId" do
|
|
47
|
+
it "should list all tasks when filtering by contextId" do
|
|
48
|
+
# Create two tasks, second one with explicit contextId
|
|
49
|
+
task1 = create_task!(text: "Context sharing 1")
|
|
50
|
+
context_id = task1["contextId"]
|
|
51
|
+
|
|
52
|
+
body2 = build_send_message_request(text: "Context sharing 2", context_id: context_id)
|
|
53
|
+
http_post("/message:send", body2)
|
|
54
|
+
|
|
55
|
+
# List tasks by context
|
|
56
|
+
response = http_get("/tasks?contextId=#{context_id}")
|
|
57
|
+
data = parse_json(response)
|
|
58
|
+
|
|
59
|
+
data["tasks"].length.should.be >= 2
|
|
60
|
+
data["tasks"].each { |t| t["contextId"].should.equal context_id }
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# --- Input Required State ---
|
|
65
|
+
# NOTE: Commented out -- reference server does not enter INPUT_REQUIRED state
|
|
66
|
+
|
|
67
|
+
# describe "when the agent needs additional input from the client" do
|
|
68
|
+
# it "should transition the task to input_required state" do
|
|
69
|
+
# end
|
|
70
|
+
# end
|
|
71
|
+
|
|
72
|
+
# describe "when a client sends a message to a task in input_required state" do
|
|
73
|
+
# it "should accept the message and continue processing the task" do
|
|
74
|
+
# end
|
|
75
|
+
# end
|
|
76
|
+
|
|
77
|
+
# --- Follow-up Messages ---
|
|
78
|
+
# NOTE: Commented out -- reference server completes tasks immediately
|
|
79
|
+
|
|
80
|
+
# describe "when a client sends a follow-up message with taskId to refine an existing task" do
|
|
81
|
+
# it "should accept the message as a refinement of the existing task" do
|
|
82
|
+
# end
|
|
83
|
+
# end
|
|
84
|
+
|
|
85
|
+
# describe "when a client sends a message with referenceTaskIds" do
|
|
86
|
+
# it "should use the referenced tasks to understand context and intent" do
|
|
87
|
+
# end
|
|
88
|
+
# end
|
|
89
|
+
|
|
90
|
+
# describe "when a client sends a message with mismatching contextId and taskId" do
|
|
91
|
+
# it "should reject the message with an error" do
|
|
92
|
+
# end
|
|
93
|
+
# end
|
|
94
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
require "a2a_test_framework/test_helper"
|
|
2
|
+
|
|
3
|
+
# Cross-cutting: Protocol data model applies to all REST endpoints
|
|
4
|
+
# Verifies that data structures conform to the A2A protocol specification.
|
|
5
|
+
|
|
6
|
+
describe "Protocol Data Model (REST)" do
|
|
7
|
+
describe "when validating Task object structure" do
|
|
8
|
+
it "should contain required fields: id, status" do
|
|
9
|
+
task = create_task!(text: "Task model test")
|
|
10
|
+
response = http_get("/tasks/#{task["id"]}")
|
|
11
|
+
data = parse_json(response)
|
|
12
|
+
|
|
13
|
+
data["id"].should.not.be.nil
|
|
14
|
+
data["status"].should.not.be.nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "should contain status with state and timestamp" do
|
|
18
|
+
task = create_task!(text: "Status model test")
|
|
19
|
+
response = http_get("/tasks/#{task["id"]}")
|
|
20
|
+
data = parse_json(response)
|
|
21
|
+
|
|
22
|
+
data["status"]["state"].should.not.be.nil
|
|
23
|
+
data["status"]["timestamp"].should.not.be.nil
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
describe "when validating Message object structure" do
|
|
28
|
+
it "should contain role and parts fields" do
|
|
29
|
+
task = create_task!(text: "Message model test")
|
|
30
|
+
response = http_get("/tasks/#{task["id"]}?historyLength=10")
|
|
31
|
+
data = parse_json(response)
|
|
32
|
+
|
|
33
|
+
if data["history"] && data["history"].length > 0
|
|
34
|
+
msg = data["history"].first
|
|
35
|
+
msg["role"].should.not.be.nil
|
|
36
|
+
msg["parts"].should.not.be.nil
|
|
37
|
+
msg["parts"].should.be.kind_of Array
|
|
38
|
+
end
|
|
39
|
+
true.should.equal true
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "should contain messageId in messages" do
|
|
43
|
+
task = create_task!(text: "MessageId test")
|
|
44
|
+
response = http_get("/tasks/#{task["id"]}?historyLength=10")
|
|
45
|
+
data = parse_json(response)
|
|
46
|
+
|
|
47
|
+
if data["history"] && data["history"].length > 0
|
|
48
|
+
msg = data["history"].first
|
|
49
|
+
msg["messageId"].should.not.be.nil
|
|
50
|
+
msg["messageId"].should.be.kind_of String
|
|
51
|
+
end
|
|
52
|
+
true.should.equal true
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
describe "when validating Part object structure" do
|
|
57
|
+
it "should contain at least one content field (text, data, url, or raw)" do
|
|
58
|
+
task = create_task!(text: "Part model test")
|
|
59
|
+
response = http_get("/tasks/#{task["id"]}?historyLength=10")
|
|
60
|
+
data = parse_json(response)
|
|
61
|
+
|
|
62
|
+
if data["history"] && data["history"].length > 0
|
|
63
|
+
part = data["history"].first["parts"].first
|
|
64
|
+
has_content = part.key?("text") || part.key?("data") || part.key?("url") || part.key?("raw")
|
|
65
|
+
has_content.should.equal true
|
|
66
|
+
end
|
|
67
|
+
true.should.equal true
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
describe "when validating Artifact object structure" do
|
|
72
|
+
it "should contain artifactId and parts" do
|
|
73
|
+
task = create_task!(text: "Artifact model test")
|
|
74
|
+
response = http_get("/tasks/#{task["id"]}")
|
|
75
|
+
data = parse_json(response)
|
|
76
|
+
|
|
77
|
+
if data["artifacts"] && data["artifacts"].length > 0
|
|
78
|
+
artifact = data["artifacts"].first
|
|
79
|
+
artifact["artifactId"].should.not.be.nil
|
|
80
|
+
artifact["parts"].should.not.be.nil
|
|
81
|
+
artifact["parts"].should.be.kind_of Array
|
|
82
|
+
end
|
|
83
|
+
true.should.equal true
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
describe "when validating SendMessageResponse structure" do
|
|
88
|
+
it "should contain exactly one of task or message at the top level" do
|
|
89
|
+
body = build_send_message_request(text: "Response model test")
|
|
90
|
+
response = http_post("/message:send", body)
|
|
91
|
+
data = parse_json(response)
|
|
92
|
+
|
|
93
|
+
has_task = data.key?("task") && !data["task"].nil?
|
|
94
|
+
has_message = data.key?("message") && !data["message"].nil?
|
|
95
|
+
|
|
96
|
+
(has_task ^ has_message).should.equal true
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require "a2a_test_framework/test_helper"
|
|
2
|
+
|
|
3
|
+
# NOTE: All tests commented out -- Reference server does not implement protocol security
|
|
4
|
+
|
|
5
|
+
# require "a2a_test_framework/test_helper"
|
|
6
|
+
|
|
7
|
+
# # NOTE: All tests commented out -- Reference server does not implement protocol security
|
|
8
|
+
|
|
9
|
+
# # describe "Protocol Security (REST)" do
|
|
10
|
+
# # describe "when using HTTP-based bindings in production" do
|
|
11
|
+
# # it "should use HTTPS for encrypted communication" do
|
|
12
|
+
# # end
|
|
13
|
+
# #
|
|
14
|
+
# # it "should not allow plain HTTP for production deployments" do
|
|
15
|
+
# # end
|
|
16
|
+
# # end
|
|
17
|
+
# #
|
|
18
|
+
# # describe "when configuring TLS" do
|
|
19
|
+
# # it "should use TLS 1.3 or higher" do
|
|
20
|
+
# # end
|
|
21
|
+
# #
|
|
22
|
+
# # it "should use strong cipher suites" do
|
|
23
|
+
# # end
|
|
24
|
+
# # end
|
|
25
|
+
# # end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
require "a2a_test_framework/test_helper"
|
|
2
|
+
|
|
3
|
+
# NOTE: All tests commented out -- Reference server does not implement protocol negotiation
|
|
4
|
+
|
|
5
|
+
# require "a2a_test_framework/test_helper"
|
|
6
|
+
|
|
7
|
+
# # NOTE: All tests commented out -- Reference server does not implement protocol negotiation
|
|
8
|
+
|
|
9
|
+
# # describe "Protocol Selection and Negotiation (REST)" do
|
|
10
|
+
# # describe "when the agent supports multiple protocols" do
|
|
11
|
+
# # it "should declare all supported protocols in the AgentCard" do
|
|
12
|
+
# # end
|
|
13
|
+
# # end
|
|
14
|
+
# #
|
|
15
|
+
# # describe "when a client chooses a protocol declared in the AgentCard" do
|
|
16
|
+
# # it "should accept requests via that protocol" do
|
|
17
|
+
# # end
|
|
18
|
+
# # end
|
|
19
|
+
# #
|
|
20
|
+
# # describe "when the client's preferred protocol is unavailable" do
|
|
21
|
+
# # it "should implement fallback logic to try alternative declared protocols" do
|
|
22
|
+
# # end
|
|
23
|
+
# # end
|
|
24
|
+
# # end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
require "a2a_test_framework/test_helper"
|
|
2
|
+
|
|
3
|
+
# NOTE: All tests commented out -- Push notification delivery requires external webhook receiver
|
|
4
|
+
|
|
5
|
+
# require "a2a_test_framework/test_helper"
|
|
6
|
+
|
|
7
|
+
# # NOTE: All tests commented out -- Push notification delivery requires external webhook receiver
|
|
8
|
+
|
|
9
|
+
# # describe "Push Notification Delivery Protocol (REST)" do
|
|
10
|
+
# # # --- Delivery Protocol ---
|
|
11
|
+
# #
|
|
12
|
+
# # describe "when a push notification is triggered regardless of agent binding" do
|
|
13
|
+
# # it "should use plain HTTP for webhook calls" do
|
|
14
|
+
# # end
|
|
15
|
+
# #
|
|
16
|
+
# # it "should use JSON format as defined in the HTTP protocol binding" do
|
|
17
|
+
# # end
|
|
18
|
+
# # end
|
|
19
|
+
# #
|
|
20
|
+
# # describe "when checking capability requirements" do
|
|
21
|
+
# # it "should only allow streaming operations if capabilities.streaming is true" do
|
|
22
|
+
# # end
|
|
23
|
+
# #
|
|
24
|
+
# # it "should only allow push notification operations if capabilities.pushNotifications is true" do
|
|
25
|
+
# # end
|
|
26
|
+
# # end
|
|
27
|
+
# #
|
|
28
|
+
# # # --- Push Notification Payload ---
|
|
29
|
+
# #
|
|
30
|
+
# # describe "when the agent sends a push notification" do
|
|
31
|
+
# # it "should use HTTP POST method" do
|
|
32
|
+
# # end
|
|
33
|
+
# #
|
|
34
|
+
# # it "should include Content-Type header set to application/a2a+json" do
|
|
35
|
+
# # end
|
|
36
|
+
# # end
|
|
37
|
+
# #
|
|
38
|
+
# # describe "when a push notification payload is delivered" do
|
|
39
|
+
# # it "should contain exactly one of task, message, statusUpdate, or artifactUpdate" do
|
|
40
|
+
# # end
|
|
41
|
+
# # end
|
|
42
|
+
# #
|
|
43
|
+
# # describe "when the payload contains a task field" do
|
|
44
|
+
# # it "should contain a valid Task object with current state" do
|
|
45
|
+
# # end
|
|
46
|
+
# # end
|
|
47
|
+
# #
|
|
48
|
+
# # describe "when the payload contains a statusUpdate field" do
|
|
49
|
+
# # it "should contain a valid TaskStatusUpdateEvent object" do
|
|
50
|
+
# # end
|
|
51
|
+
# # end
|
|
52
|
+
# #
|
|
53
|
+
# # describe "when the payload contains an artifactUpdate field" do
|
|
54
|
+
# # it "should contain a valid TaskArtifactUpdateEvent object" do
|
|
55
|
+
# # end
|
|
56
|
+
# # end
|
|
57
|
+
# #
|
|
58
|
+
# # # --- Authentication ---
|
|
59
|
+
# #
|
|
60
|
+
# # describe "when push notification config includes authentication info" do
|
|
61
|
+
# # it "should include authentication credentials in webhook request headers" do
|
|
62
|
+
# # end
|
|
63
|
+
# #
|
|
64
|
+
# # it "should match the format specified in PushNotificationConfig.authentication" do
|
|
65
|
+
# # end
|
|
66
|
+
# # end
|
|
67
|
+
# #
|
|
68
|
+
# # describe "when push notification config uses Bearer token authentication" do
|
|
69
|
+
# # it "should include an Authorization header with Bearer token" do
|
|
70
|
+
# # end
|
|
71
|
+
# # end
|
|
72
|
+
# #
|
|
73
|
+
# # # --- Client Responsibilities ---
|
|
74
|
+
# #
|
|
75
|
+
# # describe "when a client receives a valid push notification" do
|
|
76
|
+
# # it "should respond with an HTTP 2xx status code to acknowledge receipt" do
|
|
77
|
+
# # end
|
|
78
|
+
# # end
|
|
79
|
+
# #
|
|
80
|
+
# # describe "when a client receives the same notification twice" do
|
|
81
|
+
# # it "should process notifications idempotently" do
|
|
82
|
+
# # end
|
|
83
|
+
# #
|
|
84
|
+
# # it "should not cause unintended side effects from duplicates" do
|
|
85
|
+
# # end
|
|
86
|
+
# # end
|
|
87
|
+
# #
|
|
88
|
+
# # describe "when a client receives a push notification" do
|
|
89
|
+
# # it "should validate the task ID matches an expected task" do
|
|
90
|
+
# # end
|
|
91
|
+
# #
|
|
92
|
+
# # it "should verify the notification source" do
|
|
93
|
+
# # end
|
|
94
|
+
# # end
|
|
95
|
+
# #
|
|
96
|
+
# # # --- Server Delivery Guarantees ---
|
|
97
|
+
# #
|
|
98
|
+
# # describe "when a task status changes and a webhook is configured" do
|
|
99
|
+
# # it "should attempt delivery at least once" do
|
|
100
|
+
# # end
|
|
101
|
+
# # end
|
|
102
|
+
# #
|
|
103
|
+
# # describe "when a webhook delivery fails" do
|
|
104
|
+
# # it "should MAY implement retry logic with exponential backoff" do
|
|
105
|
+
# # end
|
|
106
|
+
# #
|
|
107
|
+
# # it "should include a reasonable timeout for the request" do
|
|
108
|
+
# # end
|
|
109
|
+
# # end
|
|
110
|
+
# #
|
|
111
|
+
# # describe "when a webhook endpoint consistently fails" do
|
|
112
|
+
# # it "should MAY stop attempting delivery after consecutive failures" do
|
|
113
|
+
# # end
|
|
114
|
+
# # end
|
|
115
|
+
# # end
|