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.
Files changed (92) hide show
  1. checksums.yaml +7 -0
  2. data/a2a.json +1961 -0
  3. data/a2a.proto +796 -0
  4. data/endpoints/grpc/cancel_task.json +10 -0
  5. data/endpoints/grpc/create_task_push_notification_config.json +10 -0
  6. data/endpoints/grpc/delete_task_push_notification_config.json +10 -0
  7. data/endpoints/grpc/get_extended_agent_card.json +10 -0
  8. data/endpoints/grpc/get_task.json +10 -0
  9. data/endpoints/grpc/get_task_push_notification_config.json +10 -0
  10. data/endpoints/grpc/list_task_push_notification_configs.json +10 -0
  11. data/endpoints/grpc/list_tasks.json +10 -0
  12. data/endpoints/grpc/send_message.json +10 -0
  13. data/endpoints/grpc/send_streaming_message.json +10 -0
  14. data/endpoints/grpc/subscribe_to_task.json +10 -0
  15. data/endpoints/rest/cancel_task.json +85 -0
  16. data/endpoints/rest/create_task_push_notification_config.json +104 -0
  17. data/endpoints/rest/delete_task_push_notification_config.json +46 -0
  18. data/endpoints/rest/get_extended_agent_card.json +168 -0
  19. data/endpoints/rest/get_task.json +111 -0
  20. data/endpoints/rest/get_task_push_notification_config.json +90 -0
  21. data/endpoints/rest/list_task_push_notification_configs.json +108 -0
  22. data/endpoints/rest/list_tasks.json +239 -0
  23. data/endpoints/rest/send_message.json +57 -0
  24. data/endpoints/rest/send_streaming_message.json +75 -0
  25. data/endpoints/rest/subscribe_to_task.json +68 -0
  26. data/exe/a2a-test +6 -0
  27. data/lib/a2a_test_framework/cli.rb +190 -0
  28. data/lib/a2a_test_framework/sse_client.rb +104 -0
  29. data/lib/a2a_test_framework/test_helper.rb +146 -0
  30. data/lib/a2a_test_framework/version.rb +5 -0
  31. data/lib/a2a_test_framework.rb +17 -0
  32. data/tests/grpc/cancel_task_test.rb +69 -0
  33. data/tests/grpc/create_task_push_notification_config_test.rb +79 -0
  34. data/tests/grpc/delete_task_push_notification_config_test.rb +54 -0
  35. data/tests/grpc/error_code_mappings_test.rb +39 -0
  36. data/tests/grpc/error_handling_test.rb +175 -0
  37. data/tests/grpc/get_extended_agent_card_test.rb +83 -0
  38. data/tests/grpc/get_task_push_notification_config_test.rb +39 -0
  39. data/tests/grpc/get_task_test.rb +76 -0
  40. data/tests/grpc/grpc_binding_test.rb +74 -0
  41. data/tests/grpc/list_task_push_notification_configs_test.rb +53 -0
  42. data/tests/grpc/list_tasks_test.rb +117 -0
  43. data/tests/grpc/protocol_data_model_test.rb +14 -0
  44. data/tests/grpc/send_message_test.rb +141 -0
  45. data/tests/grpc/send_streaming_message_test.rb +122 -0
  46. data/tests/grpc/streaming_event_delivery_test.rb +48 -0
  47. data/tests/grpc/subscribe_to_task_test.rb +92 -0
  48. data/tests/grpc/versioning_test.rb +32 -0
  49. data/tests/rest/agent_card_caching_test.rb +39 -0
  50. data/tests/rest/agent_card_signing_test.rb +74 -0
  51. data/tests/rest/agent_discovery_test.rb +117 -0
  52. data/tests/rest/authentication_authorization_test.rb +62 -0
  53. data/tests/rest/cancel_task_test.rb +110 -0
  54. data/tests/rest/capability_validation_test.rb +78 -0
  55. data/tests/rest/context_identifier_semantics_test.rb +75 -0
  56. data/tests/rest/create_task_push_notification_config_test.rb +122 -0
  57. data/tests/rest/custom_binding_test.rb +96 -0
  58. data/tests/rest/delete_task_push_notification_config_test.rb +103 -0
  59. data/tests/rest/error_code_mappings_test.rb +45 -0
  60. data/tests/rest/error_handling_test.rb +178 -0
  61. data/tests/rest/extension_versioning_test.rb +44 -0
  62. data/tests/rest/field_presence_optionality_test.rb +64 -0
  63. data/tests/rest/functional_equivalence_test.rb +23 -0
  64. data/tests/rest/get_extended_agent_card_test.rb +67 -0
  65. data/tests/rest/get_task_push_notification_config_test.rb +75 -0
  66. data/tests/rest/get_task_test.rb +134 -0
  67. data/tests/rest/history_length_semantics_test.rb +91 -0
  68. data/tests/rest/http_rest_binding_test.rb +114 -0
  69. data/tests/rest/iana_registrations_test.rb +47 -0
  70. data/tests/rest/idempotency_test.rb +69 -0
  71. data/tests/rest/in_task_authorization_test.rb +45 -0
  72. data/tests/rest/json_field_naming_test.rb +89 -0
  73. data/tests/rest/json_rpc_binding_test.rb +102 -0
  74. data/tests/rest/list_task_push_notification_configs_test.rb +92 -0
  75. data/tests/rest/list_tasks_test.rb +162 -0
  76. data/tests/rest/messages_and_artifacts_test.rb +101 -0
  77. data/tests/rest/multi_turn_conversation_test.rb +94 -0
  78. data/tests/rest/protocol_data_model_test.rb +99 -0
  79. data/tests/rest/protocol_security_test.rb +25 -0
  80. data/tests/rest/protocol_selection_negotiation_test.rb +24 -0
  81. data/tests/rest/push_notification_delivery_test.rb +115 -0
  82. data/tests/rest/security_considerations_test.rb +101 -0
  83. data/tests/rest/send_message_test.rb +230 -0
  84. data/tests/rest/send_streaming_message_test.rb +129 -0
  85. data/tests/rest/service_parameters_test.rb +52 -0
  86. data/tests/rest/streaming_event_delivery_test.rb +58 -0
  87. data/tests/rest/subscribe_to_task_test.rb +99 -0
  88. data/tests/rest/task_identifier_semantics_test.rb +67 -0
  89. data/tests/rest/timestamps_test.rb +70 -0
  90. data/tests/rest/versioning_responsibilities_test.rb +46 -0
  91. data/tests/rest/versioning_test.rb +44 -0
  92. metadata +159 -0
@@ -0,0 +1,190 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "optparse"
4
+ require_relative "../a2a_test_framework"
5
+
6
+ module A2ATestFramework
7
+ class CLI
8
+ CATEGORIES = {
9
+ "discovery" => %w[
10
+ agent_discovery_test
11
+ agent_card_caching_test
12
+ agent_card_signing_test
13
+ ],
14
+ "send_message" => %w[
15
+ send_message_test
16
+ ],
17
+ "streaming" => %w[
18
+ send_streaming_message_test
19
+ streaming_event_delivery_test
20
+ subscribe_to_task_test
21
+ ],
22
+ "tasks" => %w[
23
+ get_task_test
24
+ list_tasks_test
25
+ cancel_task_test
26
+ ],
27
+ "push_notifications" => %w[
28
+ push_notification_delivery_test
29
+ create_task_push_notification_config_test
30
+ get_task_push_notification_config_test
31
+ list_task_push_notification_configs_test
32
+ delete_task_push_notification_config_test
33
+ ],
34
+ "errors" => %w[
35
+ error_handling_test
36
+ error_code_mappings_test
37
+ ],
38
+ "versioning" => %w[
39
+ versioning_test
40
+ versioning_responsibilities_test
41
+ extension_versioning_test
42
+ ],
43
+ "security" => %w[
44
+ security_considerations_test
45
+ protocol_security_test
46
+ authentication_authorization_test
47
+ in_task_authorization_test
48
+ ],
49
+ "protocol" => %w[
50
+ protocol_data_model_test
51
+ json_rpc_binding_test
52
+ http_rest_binding_test
53
+ json_field_naming_test
54
+ custom_binding_test
55
+ protocol_selection_negotiation_test
56
+ functional_equivalence_test
57
+ iana_registrations_test
58
+ ],
59
+ "semantics" => %w[
60
+ context_identifier_semantics_test
61
+ task_identifier_semantics_test
62
+ history_length_semantics_test
63
+ field_presence_optionality_test
64
+ idempotency_test
65
+ multi_turn_conversation_test
66
+ messages_and_artifacts_test
67
+ timestamps_test
68
+ service_parameters_test
69
+ capability_validation_test
70
+ ],
71
+ "extended" => %w[
72
+ get_extended_agent_card_test
73
+ ],
74
+ }.freeze
75
+
76
+ def initialize(argv)
77
+ @argv = argv
78
+ @options = {
79
+ url: ENV.fetch("A2A_BASE_URL", "http://localhost:9292"),
80
+ binding: "rest",
81
+ only: [],
82
+ files: [],
83
+ }
84
+ end
85
+
86
+ def run
87
+ parse_options!
88
+
89
+ ENV["A2A_BASE_URL"] = @options[:url]
90
+
91
+ files = resolve_test_files
92
+ if files.empty?
93
+ $stderr.puts "No test files found matching the given criteria."
94
+ exit 1
95
+ end
96
+
97
+ require "bundler/setup"
98
+ require "scampi"
99
+ Scampi.summary_on_exit
100
+
101
+ files.each { |f| load f }
102
+ end
103
+
104
+ private
105
+
106
+ def parse_options!
107
+ parser = OptionParser.new do |opts|
108
+ opts.banner = "Usage: a2a-test [options] [test_files...]"
109
+ opts.separator ""
110
+ opts.separator "Run A2A protocol conformance tests against an A2A server implementation."
111
+ opts.separator ""
112
+ opts.separator "Options:"
113
+
114
+ opts.on("--url URL", "Base URL of the A2A server (default: $A2A_BASE_URL or http://localhost:9292)") do |url|
115
+ @options[:url] = url
116
+ end
117
+
118
+ opts.on("--binding BINDING", "Protocol binding to test: rest, grpc (default: rest)") do |binding|
119
+ unless %w[rest grpc].include?(binding)
120
+ $stderr.puts "Error: --binding must be 'rest' or 'grpc'"
121
+ exit 1
122
+ end
123
+ @options[:binding] = binding
124
+ end
125
+
126
+ opts.on("--only CATEGORIES", "Comma-separated list of test categories to run") do |cats|
127
+ @options[:only] = cats.split(",").map(&:strip)
128
+ invalid = @options[:only] - CATEGORIES.keys
129
+ unless invalid.empty?
130
+ $stderr.puts "Error: unknown categories: #{invalid.join(', ')}"
131
+ $stderr.puts "Available categories: #{CATEGORIES.keys.join(', ')}"
132
+ exit 1
133
+ end
134
+ end
135
+
136
+ opts.on("--list-categories", "List available test categories and exit") do
137
+ puts "Available test categories:"
138
+ puts ""
139
+ CATEGORIES.each do |name, files|
140
+ puts " #{name}"
141
+ files.each { |f| puts " - #{f}" }
142
+ puts ""
143
+ end
144
+ exit 0
145
+ end
146
+
147
+ opts.on("-v", "--version", "Print version and exit") do
148
+ puts "a2a-test-framework #{A2ATestFramework::VERSION}"
149
+ exit 0
150
+ end
151
+
152
+ opts.on("-h", "--help", "Show this help message") do
153
+ puts opts
154
+ exit 0
155
+ end
156
+ end
157
+
158
+ parser.parse!(@argv)
159
+
160
+ # Remaining arguments are test file paths
161
+ @options[:files] = @argv.dup
162
+ end
163
+
164
+ def resolve_test_files
165
+ # If specific file paths were provided, use those
166
+ unless @options[:files].empty?
167
+ return @options[:files].map { |f| File.expand_path(f) }.select { |f| File.exist?(f) }
168
+ end
169
+
170
+ binding_dir = File.join(A2ATestFramework::TESTS_DIR, @options[:binding])
171
+
172
+ unless Dir.exist?(binding_dir)
173
+ $stderr.puts "Error: test directory not found: #{binding_dir}"
174
+ exit 1
175
+ end
176
+
177
+ if @options[:only].empty?
178
+ # Run all tests for the binding
179
+ Dir.glob(File.join(binding_dir, "**/*_test.rb")).sort
180
+ else
181
+ # Filter by category
182
+ test_basenames = @options[:only].flat_map { |cat| CATEGORIES[cat] }.compact.uniq
183
+ test_basenames.filter_map do |basename|
184
+ path = File.join(binding_dir, "#{basename}.rb")
185
+ path if File.exist?(path)
186
+ end
187
+ end
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: false
2
+
3
+ require "net/http"
4
+ require "json"
5
+ require "uri"
6
+ require "timeout"
7
+
8
+ # Simple SSE client for testing streaming endpoints.
9
+ # Reads SSE events from a chunked HTTP response.
10
+ module SSEClient
11
+ Event = Struct.new(:data, :event, :id, keyword_init: true)
12
+
13
+ # POST to a streaming endpoint and collect SSE events.
14
+ # Returns an array of Event structs.
15
+ def self.post_stream(path, body, timeout_seconds: 10, headers: {})
16
+ collect_events(:post, path, body: body, timeout_seconds: timeout_seconds, headers: headers)
17
+ end
18
+
19
+ # GET a streaming endpoint and collect SSE events.
20
+ # Returns an array of Event structs.
21
+ def self.get_stream(path, timeout_seconds: 10, headers: {})
22
+ collect_events(:get, path, timeout_seconds: timeout_seconds, headers: headers)
23
+ end
24
+
25
+ private
26
+
27
+ def self.collect_events(method, path, body: nil, timeout_seconds: 10, headers: {})
28
+ uri = URI("#{BASE_URL}#{path}")
29
+ events = []
30
+
31
+ Timeout.timeout(timeout_seconds) do
32
+ Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |http|
33
+ request = case method
34
+ when :post
35
+ req = Net::HTTP::Post.new(uri.path)
36
+ req["Content-Type"] = "application/json"
37
+ req.body = JSON.generate(body) if body
38
+ req
39
+ when :get
40
+ Net::HTTP::Get.new(uri.request_uri)
41
+ end
42
+
43
+ request["Accept"] = "text/event-stream"
44
+ headers.each { |k, v| request[k] = v }
45
+
46
+ http.request(request) do |response|
47
+ buffer = ""
48
+ current_event = nil
49
+ current_data_lines = []
50
+
51
+ response.read_body do |chunk|
52
+ buffer << chunk
53
+ while (line_end = buffer.index("\n"))
54
+ line = buffer.slice!(0..line_end).chomp("\r\n").chomp("\n")
55
+
56
+ if line.empty?
57
+ # Empty line = event boundary
58
+ if current_data_lines.any?
59
+ data_str = current_data_lines.join("\n")
60
+ parsed = begin
61
+ JSON.parse(data_str)
62
+ rescue JSON::ParserError
63
+ data_str
64
+ end
65
+ events << Event.new(
66
+ data: parsed,
67
+ event: current_event
68
+ )
69
+ end
70
+ current_event = nil
71
+ current_data_lines = []
72
+ elsif line.start_with?("data: ")
73
+ current_data_lines << line.sub(/\Adata: /, "")
74
+ elsif line.start_with?("data:")
75
+ current_data_lines << line.sub(/\Adata:/, "")
76
+ elsif line.start_with?("event: ")
77
+ current_event = line.sub(/\Aevent: /, "")
78
+ elsif line.start_with?("id: ")
79
+ # ignored for now
80
+ end
81
+ end
82
+ end
83
+
84
+ # Handle any remaining data in buffer
85
+ if current_data_lines.any?
86
+ data_str = current_data_lines.join("\n")
87
+ parsed = begin
88
+ JSON.parse(data_str)
89
+ rescue JSON::ParserError
90
+ data_str
91
+ end
92
+ events << Event.new(data: parsed, event: current_event)
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ events
99
+ rescue Timeout::Error
100
+ events
101
+ rescue EOFError, IOError, Errno::ECONNRESET
102
+ events
103
+ end
104
+ end
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
4
+ require "scampi"
5
+ require "net/http"
6
+ require "json"
7
+ require "uri"
8
+ require "securerandom"
9
+ require "a2a"
10
+
11
+ BASE_URL = ENV.fetch("A2A_BASE_URL", "http://localhost:9292")
12
+
13
+ # --- HTTP Helpers -----------------------------------------------------------
14
+
15
+ def http_post(path, body, headers: {})
16
+ uri = URI("#{BASE_URL}#{path}")
17
+ http = Net::HTTP.new(uri.host, uri.port)
18
+ http.use_ssl = uri.scheme == "https"
19
+ http.read_timeout = 30
20
+ request = Net::HTTP::Post.new(uri.path)
21
+ request["Content-Type"] = "application/json"
22
+ headers.each { |k, v| request[k] = v }
23
+ request.body = JSON.generate(body)
24
+ http.request(request)
25
+ end
26
+
27
+ def http_get(path, headers: {})
28
+ uri = URI("#{BASE_URL}#{path}")
29
+ http = Net::HTTP.new(uri.host, uri.port)
30
+ http.use_ssl = uri.scheme == "https"
31
+ http.read_timeout = 30
32
+ request = Net::HTTP::Get.new(uri.request_uri)
33
+ headers.each { |k, v| request[k] = v }
34
+ http.request(request)
35
+ end
36
+
37
+ def http_delete(path, headers: {})
38
+ uri = URI("#{BASE_URL}#{path}")
39
+ http = Net::HTTP.new(uri.host, uri.port)
40
+ http.use_ssl = uri.scheme == "https"
41
+ http.read_timeout = 30
42
+ request = Net::HTTP::Delete.new(uri.path)
43
+ headers.each { |k, v| request[k] = v }
44
+ http.request(request)
45
+ end
46
+
47
+ def parse_json(response)
48
+ JSON.parse(response.body)
49
+ end
50
+
51
+ # --- Request Builders (using A2A Schema objects) ----------------------------
52
+
53
+ def build_send_message_request(text: "Hello, agent", task_id: nil, context_id: nil, configuration: nil, metadata: nil)
54
+ message = {
55
+ "messageId" => SecureRandom.uuid,
56
+ "role" => "ROLE_USER",
57
+ "parts" => [{ "text" => text }]
58
+ }
59
+ message["taskId"] = task_id if task_id
60
+ message["contextId"] = context_id if context_id
61
+
62
+ A2A::Schema["Send Message Request"].new(
63
+ message: message,
64
+ configuration: configuration,
65
+ metadata: metadata
66
+ ).to_h
67
+ end
68
+
69
+ def build_get_task_request(id:, history_length: nil)
70
+ A2A::Schema["Get Task Request"].new(
71
+ id: id,
72
+ history_length: history_length
73
+ ).to_h
74
+ end
75
+
76
+ def build_list_tasks_request(context_id: nil, status: nil, page_size: nil, page_token: nil, history_length: nil, include_artifacts: nil)
77
+ A2A::Schema["List Tasks Request"].new(
78
+ context_id: context_id,
79
+ status: status,
80
+ page_size: page_size,
81
+ page_token: page_token,
82
+ history_length: history_length,
83
+ include_artifacts: include_artifacts
84
+ ).to_h
85
+ end
86
+
87
+ def build_cancel_task_request(id:, metadata: nil)
88
+ A2A::Schema["Cancel Task Request"].new(
89
+ id: id,
90
+ metadata: metadata
91
+ ).to_h
92
+ end
93
+
94
+ def build_subscribe_to_task_request(id:)
95
+ A2A::Schema["Subscribe To Task Request"].new(id: id).to_h
96
+ end
97
+
98
+ def build_push_notification_config(task_id:, url: "https://example.com/webhook", token: nil, authentication: nil)
99
+ A2A::Schema["Task Push Notification Config"].new(
100
+ task_id: task_id,
101
+ url: url,
102
+ token: token,
103
+ authentication: authentication
104
+ ).to_h
105
+ end
106
+
107
+ def build_get_push_notification_config_request(id:, task_id:)
108
+ A2A::Schema["Get Task Push Notification Config Request"].new(
109
+ id: id,
110
+ task_id: task_id
111
+ ).to_h
112
+ end
113
+
114
+ def build_list_push_notification_configs_request(task_id:, page_size: nil, page_token: nil)
115
+ A2A::Schema["List Task Push Notification Configs Request"].new(
116
+ task_id: task_id,
117
+ page_size: page_size,
118
+ page_token: page_token
119
+ ).to_h
120
+ end
121
+
122
+ def build_delete_push_notification_config_request(id:, task_id:)
123
+ A2A::Schema["Delete Task Push Notification Config Request"].new(
124
+ id: id,
125
+ task_id: task_id
126
+ ).to_h
127
+ end
128
+
129
+ # --- Lifecycle Helpers ------------------------------------------------------
130
+
131
+ # Sends a message and returns the task hash from the response.
132
+ def create_task!(text: "Hello, agent")
133
+ body = build_send_message_request(text: text)
134
+ response = http_post("/message:send", body)
135
+ raise "Failed to create task: HTTP #{response.code}" unless response.code.to_i == 200
136
+ data = parse_json(response)
137
+ raise "No task in response" unless data["task"]
138
+ data["task"]
139
+ end
140
+
141
+ # Retrieves a task by ID.
142
+ def get_task!(id)
143
+ response = http_get("/tasks/#{id}")
144
+ raise "Failed to get task: HTTP #{response.code}" unless response.code.to_i == 200
145
+ parse_json(response)
146
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module A2ATestFramework
4
+ VERSION = "0.4.0"
5
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "a2a_test_framework/version"
4
+
5
+ module A2ATestFramework
6
+ # Root directory of the gem (for locating test files, schemas, etc.)
7
+ ROOT = File.expand_path("..", __dir__)
8
+
9
+ # Directory containing the test files
10
+ TESTS_DIR = File.join(ROOT, "tests")
11
+
12
+ # Directory containing endpoint JSON schemas
13
+ ENDPOINTS_DIR = File.join(ROOT, "endpoints")
14
+
15
+ # Path to the protocol schema bundle
16
+ SCHEMA_PATH = File.join(ROOT, "a2a.json")
17
+ end
@@ -0,0 +1,69 @@
1
+ require "a2a_test_framework/test_helper"
2
+
3
+ # NOTE: All tests commented out -- gRPC binding not supported by reference server
4
+
5
+ # require "a2a_test_framework/test_helper"
6
+
7
+ # # NOTE: All tests commented out -- gRPC binding not supported by reference server
8
+
9
+ # # describe "A2AService/CancelTask" do
10
+ # # # --- Successful Cancellation ---
11
+ # #
12
+ # # describe "when a client sends a CancelTask request for a working task" do
13
+ # # it "should respond with an updated Task object" do
14
+ # # end
15
+ # #
16
+ # # it "should reflect the cancellation in the Task status" do
17
+ # # end
18
+ # # end
19
+ # #
20
+ # # describe "when a client sends a CancelTask request for a task in input_required state" do
21
+ # # it "should respond with an updated Task object" do
22
+ # # end
23
+ # #
24
+ # # it "should reflect the cancellation in the Task status" do
25
+ # # end
26
+ # # end
27
+ # #
28
+ # # describe "when a client sends a CancelTask request for a cancelable task" do
29
+ # # it "should return an updated Task object with cancellation status" do
30
+ # # end
31
+ # # end
32
+ # #
33
+ # # # --- Error Cases ---
34
+ # #
35
+ # # describe "when a client sends a CancelTask request for a completed task" do
36
+ # # it "should respond with a TaskNotCancelableError" do
37
+ # # end
38
+ # # end
39
+ # #
40
+ # # describe "when a client sends a CancelTask request for a failed task" do
41
+ # # it "should respond with a TaskNotCancelableError" do
42
+ # # end
43
+ # # end
44
+ # #
45
+ # # describe "when a client sends a CancelTask request for an already canceled task" do
46
+ # # it "should respond with a TaskNotCancelableError" do
47
+ # # end
48
+ # # end
49
+ # #
50
+ # # describe "when a client sends a CancelTask request with a non-existent task ID" do
51
+ # # it "should respond with a TaskNotFoundError" do
52
+ # # end
53
+ # # end
54
+ # #
55
+ # # describe "when a client sends a CancelTask request for a task not accessible to the client" do
56
+ # # it "should respond with a TaskNotFoundError" do
57
+ # # end
58
+ # # end
59
+ # #
60
+ # # # --- Idempotency ---
61
+ # #
62
+ # # describe "when a client sends multiple CancelTask requests for the same task" do
63
+ # # it "should handle repeated cancellation requests idempotently" do
64
+ # # end
65
+ # #
66
+ # # it "should respond with TaskNotCancelableError or TaskNotFoundError on subsequent attempts" do
67
+ # # end
68
+ # # end
69
+ # # end
@@ -0,0 +1,79 @@
1
+ require "a2a_test_framework/test_helper"
2
+
3
+ # NOTE: All tests commented out -- gRPC binding not supported by reference server
4
+
5
+ # require "a2a_test_framework/test_helper"
6
+
7
+ # # NOTE: All tests commented out -- gRPC binding not supported by reference server
8
+
9
+ # # describe "A2AService/CreateTaskPushNotificationConfig" do
10
+ # # # --- Successful Creation ---
11
+ # #
12
+ # # describe "when a client creates a push notification config with a valid webhook URL" do
13
+ # # it "should respond with a PushNotificationConfig object" do
14
+ # # end
15
+ # #
16
+ # # it "should contain an assigned ID in the response" do
17
+ # # end
18
+ # # end
19
+ # #
20
+ # # describe "when a push notification config is created for a task" do
21
+ # # it "should establish a webhook endpoint for task update notifications" do
22
+ # # end
23
+ # #
24
+ # # it "should send HTTP POST requests to the configured webhook URL when task updates occur" do
25
+ # # end
26
+ # # end
27
+ # #
28
+ # # describe "when the task status changes after config creation" do
29
+ # # it "should send an HTTP POST request to the configured webhook URL" do
30
+ # # end
31
+ # #
32
+ # # it "should send the payload as a StreamResponse object" do
33
+ # # end
34
+ # # end
35
+ # #
36
+ # # # --- Configuration Persistence ---
37
+ # #
38
+ # # describe "when a push notification config exists for a non-terminal task" do
39
+ # # it "should remain active while the task is in a non-terminal state" do
40
+ # # end
41
+ # #
42
+ # # it "should remain active until explicitly deleted" do
43
+ # # end
44
+ # # end
45
+ # #
46
+ # # describe "when a task with push notification config reaches completed state" do
47
+ # # it "should not require the configuration to persist beyond task completion" do
48
+ # # end
49
+ # # end
50
+ # #
51
+ # # # --- Error Cases ---
52
+ # #
53
+ # # describe "when the server does not support push notifications" do
54
+ # # it "should respond with a PushNotificationNotSupportedError" do
55
+ # # end
56
+ # # end
57
+ # #
58
+ # # describe "when a client sends a request with a non-existent task ID" do
59
+ # # it "should respond with a TaskNotFoundError" do
60
+ # # end
61
+ # # end
62
+ # #
63
+ # # describe "when a client sends a request for a task not accessible to the client" do
64
+ # # it "should respond with a TaskNotFoundError" do
65
+ # # end
66
+ # # end
67
+ # #
68
+ # # # --- Capability Validation ---
69
+ # #
70
+ # # describe "when the AgentCard declares pushNotifications capability as true" do
71
+ # # it "should accept and process the request" do
72
+ # # end
73
+ # # end
74
+ # #
75
+ # # describe "when the AgentCard declares pushNotifications capability as false" do
76
+ # # it "should respond with a PushNotificationNotSupportedError" do
77
+ # # end
78
+ # # end
79
+ # # end
@@ -0,0 +1,54 @@
1
+ require "a2a_test_framework/test_helper"
2
+
3
+ # NOTE: All tests commented out -- gRPC binding not supported by reference server
4
+
5
+ # require "a2a_test_framework/test_helper"
6
+
7
+ # # NOTE: All tests commented out -- gRPC binding not supported by reference server
8
+
9
+ # # describe "A2AService/DeleteTaskPushNotificationConfig" do
10
+ # # # --- Successful Deletion ---
11
+ # #
12
+ # # describe "when a client deletes an existing push notification config" do
13
+ # # it "should respond with a confirmation of deletion" do
14
+ # # end
15
+ # #
16
+ # # it "should permanently remove the configuration" do
17
+ # # end
18
+ # #
19
+ # # it "should cause subsequent GetTaskPushNotificationConfig requests to fail" do
20
+ # # end
21
+ # # end
22
+ # #
23
+ # # describe "when a task changes status after config deletion" do
24
+ # # it "should not send notifications to the previously configured webhook URL" do
25
+ # # end
26
+ # # end
27
+ # #
28
+ # # # --- Idempotency ---
29
+ # #
30
+ # # describe "when a client sends multiple delete requests for the same config" do
31
+ # # it "should have the same effect as a single delete" do
32
+ # # end
33
+ # #
34
+ # # it "should not return an error on the second request" do
35
+ # # end
36
+ # # end
37
+ # #
38
+ # # describe "when a client deletes an already-deleted config" do
39
+ # # it "should not return an error" do
40
+ # # end
41
+ # # end
42
+ # #
43
+ # # # --- Error Cases ---
44
+ # #
45
+ # # describe "when the server does not support push notifications" do
46
+ # # it "should respond with a PushNotificationNotSupportedError" do
47
+ # # end
48
+ # # end
49
+ # #
50
+ # # describe "when a client sends a request with a non-existent task ID" do
51
+ # # it "should respond with a TaskNotFoundError" do
52
+ # # end
53
+ # # end
54
+ # # end